File indexing completed on 2024-11-10 04:56:28
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2020 Xaver Hugl <xaver.hugl@gmail.com> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 #include "drm_gpu.h" 0010 0011 #include <config-kwin.h> 0012 0013 #include "core/gbmgraphicsbufferallocator.h" 0014 #include "core/session.h" 0015 #include "drm_backend.h" 0016 #include "drm_buffer.h" 0017 #include "drm_commit.h" 0018 #include "drm_connector.h" 0019 #include "drm_crtc.h" 0020 #include "drm_egl_backend.h" 0021 #include "drm_layer.h" 0022 #include "drm_logging.h" 0023 #include "drm_output.h" 0024 #include "drm_pipeline.h" 0025 #include "drm_plane.h" 0026 #include "drm_virtual_output.h" 0027 // system 0028 #include <algorithm> 0029 #include <errno.h> 0030 #include <fcntl.h> 0031 #include <poll.h> 0032 #include <unistd.h> 0033 // drm 0034 #include <drm_fourcc.h> 0035 #include <gbm.h> 0036 #include <libdrm/drm_mode.h> 0037 #include <xf86drm.h> 0038 #include <xf86drmMode.h> 0039 0040 #ifndef DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT 0041 #define DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT 6 0042 #endif 0043 0044 namespace KWin 0045 { 0046 0047 DrmGpu::DrmGpu(DrmBackend *backend, const QString &devNode, int fd, dev_t deviceId) 0048 : m_fd(fd) 0049 , m_deviceId(deviceId) 0050 , m_devNode(devNode) 0051 , m_atomicModeSetting(false) 0052 , m_gbmDevice(nullptr) 0053 , m_platform(backend) 0054 { 0055 uint64_t capability = 0; 0056 0057 if (drmGetCap(fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) { 0058 m_cursorSize.setWidth(capability); 0059 } else { 0060 m_cursorSize.setWidth(64); 0061 } 0062 0063 if (drmGetCap(fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) { 0064 m_cursorSize.setHeight(capability); 0065 } else { 0066 m_cursorSize.setHeight(64); 0067 } 0068 0069 int ret = drmGetCap(fd, DRM_CAP_TIMESTAMP_MONOTONIC, &capability); 0070 if (ret == 0 && capability == 1) { 0071 m_presentationClock = CLOCK_MONOTONIC; 0072 } else { 0073 m_presentationClock = CLOCK_REALTIME; 0074 } 0075 0076 m_addFB2ModifiersSupported = drmGetCap(fd, DRM_CAP_ADDFB2_MODIFIERS, &capability) == 0 && capability == 1; 0077 qCDebug(KWIN_DRM) << "drmModeAddFB2WithModifiers is" << (m_addFB2ModifiersSupported ? "supported" : "not supported") << "on GPU" << m_devNode; 0078 0079 // find out what driver this kms device is using 0080 DrmUniquePtr<drmVersion> version(drmGetVersion(fd)); 0081 m_isI915 = strstr(version->name, "i915"); 0082 m_isNVidia = strstr(version->name, "nvidia-drm"); 0083 m_isVirtualMachine = strstr(version->name, "virtio") || strstr(version->name, "qxl") 0084 || strstr(version->name, "vmwgfx") || strstr(version->name, "vboxvideo"); 0085 0086 // Reopen the drm node to create a new GEM handle namespace. 0087 m_gbmFd = FileDescriptor{open(devNode.toLocal8Bit(), O_RDWR | O_CLOEXEC)}; 0088 if (m_gbmFd.isValid()) { 0089 drm_magic_t magic; 0090 drmGetMagic(m_gbmFd.get(), &magic); 0091 drmAuthMagic(m_fd, magic); 0092 m_gbmDevice = gbm_create_device(m_gbmFd.get()); 0093 if (!m_gbmDevice) { 0094 qCCritical(KWIN_DRM) << "gbm_create_device() failed"; 0095 } else { 0096 m_allocator = std::make_unique<GbmGraphicsBufferAllocator>(m_gbmDevice); 0097 } 0098 } 0099 0100 m_socketNotifier = std::make_unique<QSocketNotifier>(fd, QSocketNotifier::Read); 0101 connect(m_socketNotifier.get(), &QSocketNotifier::activated, this, &DrmGpu::dispatchEvents); 0102 0103 initDrmResources(); 0104 0105 if (m_atomicModeSetting == false) { 0106 // only supported with legacy 0107 m_asyncPageflipSupported = drmGetCap(fd, DRM_CAP_ASYNC_PAGE_FLIP, &capability) == 0 && capability == 1; 0108 } 0109 } 0110 0111 DrmGpu::~DrmGpu() 0112 { 0113 removeOutputs(); 0114 m_eglDisplay.reset(); 0115 m_crtcs.clear(); 0116 m_connectors.clear(); 0117 m_planes.clear(); 0118 m_socketNotifier.reset(); 0119 m_allocator.reset(); 0120 if (m_gbmDevice) { 0121 gbm_device_destroy(m_gbmDevice); 0122 } 0123 m_gbmFd = FileDescriptor{}; 0124 m_platform->session()->closeRestricted(m_fd); 0125 } 0126 0127 FileDescriptor DrmGpu::createNonMasterFd() const 0128 { 0129 char *path = drmGetDeviceNameFromFd2(m_fd); 0130 FileDescriptor fd{open(path, O_RDWR | O_CLOEXEC)}; 0131 if (!fd.isValid()) { 0132 qCWarning(KWIN_DRM) << "Could not open DRM fd for leasing!" << strerror(errno); 0133 } else { 0134 if (drmIsMaster(fd.get())) { 0135 if (drmDropMaster(fd.get()) != 0) { 0136 qCWarning(KWIN_DRM) << "Could not create a non-master DRM fd for leasing!" << strerror(errno); 0137 return FileDescriptor{}; 0138 } 0139 } 0140 } 0141 return fd; 0142 } 0143 0144 clockid_t DrmGpu::presentationClock() const 0145 { 0146 return m_presentationClock; 0147 } 0148 0149 void DrmGpu::initDrmResources() 0150 { 0151 // try atomic mode setting 0152 bool isEnvVarSet = false; 0153 const bool supportsVmCursorHotspot = drmSetClientCap(m_fd, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT, 1) == 0; 0154 const bool noAMS = qEnvironmentVariableIntValue("KWIN_DRM_NO_AMS", &isEnvVarSet) != 0 && isEnvVarSet; 0155 if (m_isVirtualMachine && !supportsVmCursorHotspot && !isEnvVarSet) { 0156 qCWarning(KWIN_DRM, "Atomic Mode Setting disabled on GPU %s because of cursor offset issues in virtual machines", qPrintable(m_devNode)); 0157 } else if (noAMS) { 0158 qCWarning(KWIN_DRM) << "Atomic Mode Setting requested off via environment variable. Using legacy mode on GPU" << m_devNode; 0159 } else if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) != 0) { 0160 qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode on GPU" << m_devNode; 0161 } else { 0162 DrmUniquePtr<drmModePlaneRes> planeResources(drmModeGetPlaneResources(m_fd)); 0163 if (planeResources) { 0164 qCDebug(KWIN_DRM) << "Using Atomic Mode Setting on gpu" << m_devNode; 0165 qCDebug(KWIN_DRM) << "Number of planes on GPU" << m_devNode << ":" << planeResources->count_planes; 0166 // create the plane objects 0167 for (unsigned int i = 0; i < planeResources->count_planes; ++i) { 0168 DrmUniquePtr<drmModePlane> kplane(drmModeGetPlane(m_fd, planeResources->planes[i])); 0169 auto plane = std::make_unique<DrmPlane>(this, kplane->plane_id); 0170 if (plane->init()) { 0171 m_allObjects << plane.get(); 0172 m_planes.push_back(std::move(plane)); 0173 } 0174 } 0175 if (m_planes.empty()) { 0176 qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode on GPU " << m_devNode; 0177 } 0178 } else { 0179 qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode on GPU " << m_devNode; 0180 } 0181 } 0182 m_atomicModeSetting = !m_planes.empty(); 0183 0184 DrmUniquePtr<drmModeRes> resources(drmModeGetResources(m_fd)); 0185 if (!resources) { 0186 qCCritical(KWIN_DRM) << "drmModeGetResources for getting CRTCs failed on GPU" << m_devNode; 0187 return; 0188 } 0189 QList<DrmPlane *> assignedPlanes; 0190 for (int i = 0; i < resources->count_crtcs; ++i) { 0191 uint32_t crtcId = resources->crtcs[i]; 0192 QList<DrmPlane *> primaryCandidates; 0193 QList<DrmPlane *> cursorCandidates; 0194 for (const auto &plane : m_planes) { 0195 if (plane->isCrtcSupported(i) && !assignedPlanes.contains(plane.get())) { 0196 if (plane->type.enumValue() == DrmPlane::TypeIndex::Primary) { 0197 primaryCandidates.push_back(plane.get()); 0198 } else if (plane->type.enumValue() == DrmPlane::TypeIndex::Cursor) { 0199 cursorCandidates.push_back(plane.get()); 0200 } 0201 } 0202 } 0203 if (m_atomicModeSetting && primaryCandidates.empty()) { 0204 qCWarning(KWIN_DRM) << "Could not find a suitable primary plane for crtc" << resources->crtcs[i]; 0205 continue; 0206 } 0207 const auto findBestPlane = [crtcId](const QList<DrmPlane *> &list) { 0208 // if the plane is already used with this crtc, prefer it 0209 const auto connected = std::find_if(list.begin(), list.end(), [crtcId](DrmPlane *plane) { 0210 return plane->crtcId.value() == crtcId; 0211 }); 0212 if (connected != list.end()) { 0213 return *connected; 0214 } 0215 // don't take away planes from other crtcs. The kernel currently rejects such commits 0216 const auto notconnected = std::find_if(list.begin(), list.end(), [](DrmPlane *plane) { 0217 return plane->crtcId.value() == 0; 0218 }); 0219 if (notconnected != list.end()) { 0220 return *notconnected; 0221 } 0222 return list.empty() ? nullptr : list.front(); 0223 }; 0224 DrmPlane *primary = findBestPlane(primaryCandidates); 0225 DrmPlane *cursor = findBestPlane(cursorCandidates); 0226 assignedPlanes.push_back(primary); 0227 if (cursor) { 0228 assignedPlanes.push_back(cursor); 0229 } 0230 auto crtc = std::make_unique<DrmCrtc>(this, crtcId, i, primary, cursor); 0231 if (!crtc->init()) { 0232 continue; 0233 } 0234 m_allObjects << crtc.get(); 0235 m_crtcs.push_back(std::move(crtc)); 0236 } 0237 } 0238 0239 bool DrmGpu::updateOutputs() 0240 { 0241 if (!m_isActive) { 0242 return false; 0243 } 0244 waitIdle(); 0245 DrmUniquePtr<drmModeRes> resources(drmModeGetResources(m_fd)); 0246 if (!resources) { 0247 qCWarning(KWIN_DRM) << "drmModeGetResources failed"; 0248 return false; 0249 } 0250 0251 // In principle these things are supposed to be detected through the wayland protocol. 0252 // In practice SteamVR doesn't always behave correctly 0253 DrmUniquePtr<drmModeLesseeListRes> lessees{drmModeListLessees(m_fd)}; 0254 for (const auto &output : std::as_const(m_drmOutputs)) { 0255 if (output->lease()) { 0256 bool leaseActive = false; 0257 for (uint i = 0; i < lessees->count; i++) { 0258 if (lessees->lessees[i] == output->lease()->lesseeId()) { 0259 leaseActive = true; 0260 break; 0261 } 0262 } 0263 if (!leaseActive) { 0264 Q_EMIT output->lease()->revokeRequested(); 0265 } 0266 } 0267 } 0268 0269 // check for added and removed connectors 0270 QList<DrmConnector *> existing; 0271 QList<DrmOutput *> addedOutputs; 0272 for (int i = 0; i < resources->count_connectors; ++i) { 0273 const uint32_t currentConnector = resources->connectors[i]; 0274 const auto it = std::find_if(m_connectors.begin(), m_connectors.end(), [currentConnector](const auto &connector) { 0275 return connector->id() == currentConnector; 0276 }); 0277 if (it == m_connectors.end()) { 0278 auto conn = std::make_shared<DrmConnector>(this, currentConnector); 0279 if (!conn->init()) { 0280 continue; 0281 } 0282 existing.push_back(conn.get()); 0283 m_allObjects.push_back(conn.get()); 0284 m_connectors.push_back(std::move(conn)); 0285 } else { 0286 (*it)->updateProperties(); 0287 existing.push_back(it->get()); 0288 } 0289 } 0290 for (auto it = m_connectors.begin(); it != m_connectors.end();) { 0291 DrmConnector *conn = it->get(); 0292 const auto output = findOutput(conn->id()); 0293 const bool stillExists = existing.contains(conn); 0294 if (!stillExists || !conn->isConnected()) { 0295 if (output) { 0296 removeOutput(output); 0297 } 0298 } else if (!output) { 0299 qCDebug(KWIN_DRM, "New %soutput on GPU %s: %s", conn->isNonDesktop() ? "non-desktop " : "", qPrintable(m_devNode), qPrintable(conn->modelName())); 0300 const auto pipeline = conn->pipeline(); 0301 m_pipelines << pipeline; 0302 auto output = new DrmOutput(*it); 0303 m_drmOutputs << output; 0304 addedOutputs << output; 0305 Q_EMIT outputAdded(output); 0306 pipeline->setLayers(m_platform->renderBackend()->createPrimaryLayer(pipeline), m_platform->renderBackend()->createCursorLayer(pipeline)); 0307 pipeline->setActive(!conn->isNonDesktop()); 0308 pipeline->applyPendingChanges(); 0309 } 0310 if (stillExists) { 0311 it++; 0312 } else { 0313 m_allObjects.removeOne(it->get()); 0314 it = m_connectors.erase(it); 0315 } 0316 } 0317 0318 // update crtc properties 0319 for (const auto &crtc : std::as_const(m_crtcs)) { 0320 crtc->updateProperties(); 0321 } 0322 // update plane properties 0323 for (const auto &plane : std::as_const(m_planes)) { 0324 plane->updateProperties(); 0325 } 0326 DrmPipeline::Error err = testPendingConfiguration(); 0327 if (err == DrmPipeline::Error::None) { 0328 for (const auto &pipeline : std::as_const(m_pipelines)) { 0329 pipeline->applyPendingChanges(); 0330 if (pipeline->output() && !pipeline->crtc()) { 0331 pipeline->setEnable(false); 0332 pipeline->output()->updateEnabled(false); 0333 } 0334 } 0335 } else if (err == DrmPipeline::Error::NoPermission) { 0336 for (const auto &pipeline : std::as_const(m_pipelines)) { 0337 pipeline->revertPendingChanges(); 0338 } 0339 for (const auto &output : std::as_const(addedOutputs)) { 0340 removeOutput(output); 0341 const auto it = std::find_if(m_connectors.begin(), m_connectors.end(), [output](const auto &conn) { 0342 return conn.get() == output->connector(); 0343 }); 0344 Q_ASSERT(it != m_connectors.end()); 0345 m_allObjects.removeOne(it->get()); 0346 m_connectors.erase(it); 0347 } 0348 } else { 0349 qCWarning(KWIN_DRM, "Failed to find a working setup for new outputs!"); 0350 for (const auto &pipeline : std::as_const(m_pipelines)) { 0351 pipeline->revertPendingChanges(); 0352 } 0353 for (const auto &output : std::as_const(addedOutputs)) { 0354 output->updateEnabled(false); 0355 output->pipeline()->setEnable(false); 0356 output->pipeline()->applyPendingChanges(); 0357 } 0358 } 0359 return true; 0360 } 0361 0362 void DrmGpu::removeOutputs() 0363 { 0364 const auto outputs = m_drmOutputs; 0365 for (const auto &output : outputs) { 0366 removeOutput(output); 0367 } 0368 const auto virtualOutputs = m_virtualOutputs; 0369 for (const auto &output : virtualOutputs) { 0370 removeVirtualOutput(output); 0371 } 0372 } 0373 0374 DrmPipeline::Error DrmGpu::checkCrtcAssignment(QList<DrmConnector *> connectors, const QList<DrmCrtc *> &crtcs) 0375 { 0376 if (connectors.isEmpty() || crtcs.isEmpty()) { 0377 if (m_pipelines.isEmpty()) { 0378 // nothing to do 0379 return DrmPipeline::Error::None; 0380 } 0381 // remaining connectors can't be powered 0382 for (const auto &conn : std::as_const(connectors)) { 0383 qCWarning(KWIN_DRM) << "disabling connector" << conn->modelName() << "without a crtc"; 0384 conn->pipeline()->setCrtc(nullptr); 0385 } 0386 return testPipelines(); 0387 } 0388 auto connector = connectors.takeFirst(); 0389 auto pipeline = connector->pipeline(); 0390 if (!pipeline->enabled() || !connector->isConnected()) { 0391 // disabled pipelines don't need CRTCs 0392 pipeline->setCrtc(nullptr); 0393 return checkCrtcAssignment(connectors, crtcs); 0394 } 0395 DrmCrtc *currentCrtc = nullptr; 0396 if (m_atomicModeSetting) { 0397 // try the crtc that this connector is already connected to first 0398 const uint32_t id = connector->crtcId.value(); 0399 auto it = std::find_if(crtcs.begin(), crtcs.end(), [id](const auto &crtc) { 0400 return id == crtc->id(); 0401 }); 0402 if (it != crtcs.end()) { 0403 currentCrtc = *it; 0404 auto crtcsLeft = crtcs; 0405 crtcsLeft.removeOne(currentCrtc); 0406 pipeline->setCrtc(currentCrtc); 0407 do { 0408 DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcsLeft); 0409 if (err == DrmPipeline::Error::None || err == DrmPipeline::Error::NoPermission || err == DrmPipeline::Error::FramePending) { 0410 return err; 0411 } 0412 } while (pipeline->pruneModifier()); 0413 } 0414 } 0415 for (const auto &crtc : std::as_const(crtcs)) { 0416 if (connector->isCrtcSupported(crtc) && crtc != currentCrtc) { 0417 auto crtcsLeft = crtcs; 0418 crtcsLeft.removeOne(crtc); 0419 pipeline->setCrtc(crtc); 0420 do { 0421 DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcsLeft); 0422 if (err == DrmPipeline::Error::None || err == DrmPipeline::Error::NoPermission || err == DrmPipeline::Error::FramePending) { 0423 return err; 0424 } 0425 } while (pipeline->pruneModifier()); 0426 } 0427 } 0428 return DrmPipeline::Error::InvalidArguments; 0429 } 0430 0431 DrmPipeline::Error DrmGpu::testPendingConfiguration() 0432 { 0433 QList<DrmConnector *> connectors; 0434 QList<DrmCrtc *> crtcs; 0435 // only change resources that aren't currently leased away 0436 for (const auto &conn : m_connectors) { 0437 bool isLeased = std::any_of(m_drmOutputs.cbegin(), m_drmOutputs.cend(), [&conn](const auto output) { 0438 return output->lease() && output->pipeline()->connector() == conn.get(); 0439 }); 0440 if (!isLeased) { 0441 connectors.push_back(conn.get()); 0442 } 0443 } 0444 for (const auto &crtc : m_crtcs) { 0445 bool isLeased = std::any_of(m_drmOutputs.cbegin(), m_drmOutputs.cend(), [&crtc](const auto output) { 0446 return output->lease() && output->pipeline()->crtc() == crtc.get(); 0447 }); 0448 if (!isLeased) { 0449 crtcs.push_back(crtc.get()); 0450 } 0451 } 0452 if (m_atomicModeSetting) { 0453 // sort outputs by being already connected (to any CRTC) so that already working outputs get preferred 0454 std::sort(connectors.begin(), connectors.end(), [](auto c1, auto c2) { 0455 return c1->crtcId.value() > c2->crtcId.value(); 0456 }); 0457 } 0458 return checkCrtcAssignment(connectors, crtcs); 0459 } 0460 0461 DrmPipeline::Error DrmGpu::testPipelines() 0462 { 0463 QList<DrmPipeline *> inactivePipelines; 0464 std::copy_if(m_pipelines.constBegin(), m_pipelines.constEnd(), std::back_inserter(inactivePipelines), [](const auto pipeline) { 0465 return pipeline->enabled() && !pipeline->active(); 0466 }); 0467 DrmPipeline::Error test = DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::TestAllowModeset, unusedObjects()); 0468 if (!inactivePipelines.isEmpty() && test == DrmPipeline::Error::None) { 0469 // ensure that pipelines that are set as enabled but currently inactive 0470 // still work when they need to be set active again 0471 for (const auto pipeline : std::as_const(inactivePipelines)) { 0472 pipeline->setActive(true); 0473 } 0474 test = DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::TestAllowModeset, unusedObjects()); 0475 for (const auto pipeline : std::as_const(inactivePipelines)) { 0476 pipeline->setActive(false); 0477 } 0478 } 0479 return test; 0480 } 0481 0482 DrmOutput *DrmGpu::findOutput(quint32 connector) 0483 { 0484 auto it = std::find_if(m_drmOutputs.constBegin(), m_drmOutputs.constEnd(), [connector](DrmOutput *o) { 0485 return o->connector()->id() == connector; 0486 }); 0487 if (it != m_drmOutputs.constEnd()) { 0488 return *it; 0489 } 0490 return nullptr; 0491 } 0492 0493 void DrmGpu::waitIdle() 0494 { 0495 m_socketNotifier->setEnabled(false); 0496 while (true) { 0497 const bool idle = std::all_of(m_drmOutputs.constBegin(), m_drmOutputs.constEnd(), [](DrmOutput *output) { 0498 return !output->pipeline()->pageflipsPending(); 0499 }); 0500 if (idle) { 0501 break; 0502 } 0503 pollfd pfds[1]; 0504 pfds[0].fd = m_fd; 0505 pfds[0].events = POLLIN; 0506 0507 const int ready = poll(pfds, 1, 30000); 0508 if (ready < 0) { 0509 if (errno != EINTR) { 0510 qCWarning(KWIN_DRM) << Q_FUNC_INFO << "poll() failed:" << strerror(errno); 0511 break; 0512 } 0513 } else if (ready == 0) { 0514 qCWarning(KWIN_DRM) << "No drm events for gpu" << m_devNode << "within last 30 seconds"; 0515 break; 0516 } else { 0517 dispatchEvents(); 0518 } 0519 }; 0520 m_socketNotifier->setEnabled(true); 0521 } 0522 0523 static std::chrono::nanoseconds convertTimestamp(const timespec ×tamp) 0524 { 0525 return std::chrono::seconds(timestamp.tv_sec) + std::chrono::nanoseconds(timestamp.tv_nsec); 0526 } 0527 0528 static std::chrono::nanoseconds convertTimestamp(clockid_t sourceClock, clockid_t targetClock, 0529 const timespec ×tamp) 0530 { 0531 if (sourceClock == targetClock) { 0532 return convertTimestamp(timestamp); 0533 } 0534 0535 timespec sourceCurrentTime = {}; 0536 timespec targetCurrentTime = {}; 0537 0538 clock_gettime(sourceClock, &sourceCurrentTime); 0539 clock_gettime(targetClock, &targetCurrentTime); 0540 0541 const auto delta = convertTimestamp(sourceCurrentTime) - convertTimestamp(timestamp); 0542 return convertTimestamp(targetCurrentTime) - delta; 0543 } 0544 0545 void DrmGpu::pageFlipHandler(int fd, unsigned int sequence, unsigned int sec, unsigned int usec, unsigned int crtc_id, void *user_data) 0546 { 0547 const auto commit = static_cast<DrmCommit *>(user_data); 0548 const auto gpu = commit->gpu(); 0549 0550 // The static_cast<> here are for a 32-bit environment where 0551 // sizeof(time_t) == sizeof(unsigned int) == 4 . Putting @p sec 0552 // into a time_t cuts off the most-significant bit (after the 0553 // year 2038), similarly long can't hold all the bits of an 0554 // unsigned multiplication. 0555 std::chrono::nanoseconds timestamp = convertTimestamp(gpu->presentationClock(), CLOCK_MONOTONIC, 0556 {static_cast<time_t>(sec), static_cast<long>(usec * 1000)}); 0557 if (timestamp == std::chrono::nanoseconds::zero()) { 0558 qCDebug(KWIN_DRM, "Got invalid timestamp (sec: %u, usec: %u) on gpu %s", 0559 sec, usec, qPrintable(gpu->devNode())); 0560 timestamp = std::chrono::steady_clock::now().time_since_epoch(); 0561 } 0562 commit->pageFlipped(timestamp); 0563 } 0564 0565 void DrmGpu::dispatchEvents() 0566 { 0567 drmEventContext context = {}; 0568 context.version = 3; 0569 context.page_flip_handler2 = pageFlipHandler; 0570 drmHandleEvent(m_fd, &context); 0571 } 0572 0573 void DrmGpu::removeOutput(DrmOutput *output) 0574 { 0575 qCDebug(KWIN_DRM) << "Removing output" << output; 0576 m_pipelines.removeOne(output->pipeline()); 0577 output->pipeline()->setLayers(nullptr, nullptr); 0578 m_drmOutputs.removeOne(output); 0579 Q_EMIT outputRemoved(output); 0580 output->unref(); 0581 } 0582 0583 DrmBackend *DrmGpu::platform() const 0584 { 0585 return m_platform; 0586 } 0587 0588 const QList<DrmPipeline *> DrmGpu::pipelines() const 0589 { 0590 return m_pipelines; 0591 } 0592 0593 DrmVirtualOutput *DrmGpu::createVirtualOutput(const QString &name, const QSize &size, double scale) 0594 { 0595 auto output = new DrmVirtualOutput(name, this, size, scale); 0596 m_virtualOutputs << output; 0597 Q_EMIT outputAdded(output); 0598 return output; 0599 } 0600 0601 void DrmGpu::removeVirtualOutput(DrmVirtualOutput *output) 0602 { 0603 if (m_virtualOutputs.removeOne(output)) { 0604 Q_EMIT outputRemoved(output); 0605 output->unref(); 0606 } 0607 } 0608 0609 std::unique_ptr<DrmLease> DrmGpu::leaseOutputs(const QList<DrmOutput *> &outputs) 0610 { 0611 QList<uint32_t> objects; 0612 for (DrmOutput *output : outputs) { 0613 if (output->lease() || !output->addLeaseObjects(objects)) { 0614 return nullptr; 0615 } 0616 } 0617 0618 uint32_t lesseeId; 0619 FileDescriptor fd{drmModeCreateLease(m_fd, objects.constData(), objects.count(), 0, &lesseeId)}; 0620 if (!fd.isValid()) { 0621 qCWarning(KWIN_DRM) << "Could not create DRM lease!" << strerror(errno); 0622 qCWarning(KWIN_DRM) << "Tried to lease the following" << objects.count() << "resources:"; 0623 for (const auto &res : std::as_const(objects)) { 0624 qCWarning(KWIN_DRM) << res; 0625 } 0626 return nullptr; 0627 } else { 0628 qCDebug(KWIN_DRM) << "Created lease for" << objects.count() << "resources:"; 0629 for (const auto &res : std::as_const(objects)) { 0630 qCDebug(KWIN_DRM) << res; 0631 } 0632 return std::make_unique<DrmLease>(this, std::move(fd), lesseeId, outputs); 0633 } 0634 } 0635 0636 QList<DrmVirtualOutput *> DrmGpu::virtualOutputs() const 0637 { 0638 return m_virtualOutputs; 0639 } 0640 0641 QList<DrmOutput *> DrmGpu::drmOutputs() const 0642 { 0643 return m_drmOutputs; 0644 } 0645 0646 int DrmGpu::fd() const 0647 { 0648 return m_fd; 0649 } 0650 0651 dev_t DrmGpu::deviceId() const 0652 { 0653 return m_deviceId; 0654 } 0655 0656 bool DrmGpu::atomicModeSetting() const 0657 { 0658 return m_atomicModeSetting; 0659 } 0660 0661 QString DrmGpu::devNode() const 0662 { 0663 return m_devNode; 0664 } 0665 0666 gbm_device *DrmGpu::gbmDevice() const 0667 { 0668 return m_gbmDevice; 0669 } 0670 0671 EglDisplay *DrmGpu::eglDisplay() const 0672 { 0673 return m_eglDisplay.get(); 0674 } 0675 0676 void DrmGpu::setEglDisplay(std::unique_ptr<EglDisplay> &&display) 0677 { 0678 m_eglDisplay = std::move(display); 0679 } 0680 0681 bool DrmGpu::addFB2ModifiersSupported() const 0682 { 0683 return m_addFB2ModifiersSupported; 0684 } 0685 0686 bool DrmGpu::asyncPageflipSupported() const 0687 { 0688 return m_asyncPageflipSupported; 0689 } 0690 0691 bool DrmGpu::isI915() const 0692 { 0693 return m_isI915; 0694 } 0695 0696 bool DrmGpu::isNVidia() const 0697 { 0698 return m_isNVidia; 0699 } 0700 0701 bool DrmGpu::isRemoved() const 0702 { 0703 return m_isRemoved; 0704 } 0705 0706 void DrmGpu::setRemoved() 0707 { 0708 m_isRemoved = true; 0709 } 0710 0711 void DrmGpu::setActive(bool active) 0712 { 0713 if (m_isActive != active) { 0714 m_isActive = active; 0715 if (active) { 0716 for (const auto &output : std::as_const(m_drmOutputs)) { 0717 output->renderLoop()->uninhibit(); 0718 } 0719 // while the session was inactive, the output list may have changed 0720 m_platform->updateOutputs(); 0721 for (const auto &output : std::as_const(m_drmOutputs)) { 0722 // force a modeset with legacy, we can't reliably know if one is needed 0723 if (!atomicModeSetting()) { 0724 output->pipeline()->forceLegacyModeset(); 0725 } 0726 } 0727 } else { 0728 for (const auto &output : std::as_const(m_drmOutputs)) { 0729 output->renderLoop()->inhibit(); 0730 } 0731 } 0732 Q_EMIT activeChanged(active); 0733 } 0734 } 0735 0736 bool DrmGpu::isActive() const 0737 { 0738 return m_isActive; 0739 } 0740 0741 bool DrmGpu::needsModeset() const 0742 { 0743 return std::any_of(m_pipelines.constBegin(), m_pipelines.constEnd(), [](const auto &pipeline) { 0744 return pipeline->needsModeset(); 0745 }); 0746 } 0747 0748 bool DrmGpu::maybeModeset() 0749 { 0750 auto pipelines = m_pipelines; 0751 for (const auto &output : std::as_const(m_drmOutputs)) { 0752 if (output->lease()) { 0753 pipelines.removeOne(output->pipeline()); 0754 } 0755 } 0756 bool presentPendingForAll = std::all_of(pipelines.constBegin(), pipelines.constEnd(), [](const auto &pipeline) { 0757 return pipeline->modesetPresentPending() || !pipeline->activePending(); 0758 }); 0759 if (!presentPendingForAll) { 0760 // commit only once all pipelines are ready for presentation 0761 return true; 0762 } 0763 // make sure there's no pending pageflips 0764 waitIdle(); 0765 const DrmPipeline::Error err = DrmPipeline::commitPipelines(pipelines, DrmPipeline::CommitMode::CommitModeset, unusedObjects()); 0766 for (DrmPipeline *pipeline : std::as_const(pipelines)) { 0767 if (pipeline->modesetPresentPending()) { 0768 pipeline->resetModesetPresentPending(); 0769 if (err != DrmPipeline::Error::None) { 0770 pipeline->output()->frameFailed(); 0771 } 0772 } 0773 } 0774 if (err == DrmPipeline::Error::None) { 0775 return true; 0776 } else { 0777 if (err != DrmPipeline::Error::FramePending) { 0778 QTimer::singleShot(0, m_platform, &DrmBackend::updateOutputs); 0779 } 0780 return false; 0781 } 0782 } 0783 0784 QList<DrmObject *> DrmGpu::unusedObjects() const 0785 { 0786 if (!m_atomicModeSetting) { 0787 return {}; 0788 } 0789 QList<DrmObject *> ret = m_allObjects; 0790 for (const auto &pipeline : m_pipelines) { 0791 ret.removeOne(pipeline->connector()); 0792 if (pipeline->crtc()) { 0793 ret.removeOne(pipeline->crtc()); 0794 ret.removeOne(pipeline->crtc()->primaryPlane()); 0795 ret.removeOne(pipeline->crtc()->cursorPlane()); 0796 } 0797 } 0798 return ret; 0799 } 0800 0801 QSize DrmGpu::cursorSize() const 0802 { 0803 return m_cursorSize; 0804 } 0805 0806 void DrmGpu::releaseBuffers() 0807 { 0808 for (const auto &plane : std::as_const(m_planes)) { 0809 plane->releaseCurrentBuffer(); 0810 } 0811 for (const auto &crtc : std::as_const(m_crtcs)) { 0812 crtc->releaseCurrentBuffer(); 0813 } 0814 for (const auto &pipeline : std::as_const(m_pipelines)) { 0815 pipeline->primaryLayer()->releaseBuffers(); 0816 pipeline->cursorLayer()->releaseBuffers(); 0817 } 0818 for (const auto &output : std::as_const(m_virtualOutputs)) { 0819 output->primaryLayer()->releaseBuffers(); 0820 } 0821 } 0822 0823 void DrmGpu::recreateSurfaces() 0824 { 0825 for (const auto &pipeline : std::as_const(m_pipelines)) { 0826 pipeline->setLayers(m_platform->renderBackend()->createPrimaryLayer(pipeline), m_platform->renderBackend()->createCursorLayer(pipeline)); 0827 pipeline->applyPendingChanges(); 0828 } 0829 for (const auto &output : std::as_const(m_virtualOutputs)) { 0830 output->recreateSurface(); 0831 } 0832 } 0833 0834 GraphicsBufferAllocator *DrmGpu::graphicsBufferAllocator() const 0835 { 0836 return m_allocator.get(); 0837 } 0838 0839 std::shared_ptr<DrmFramebuffer> DrmGpu::importBuffer(GraphicsBuffer *buffer, FileDescriptor &&readFence) 0840 { 0841 const DmaBufAttributes *attributes = buffer->dmabufAttributes(); 0842 if (Q_UNLIKELY(!attributes)) { 0843 return nullptr; 0844 } 0845 0846 uint32_t handles[] = {0, 0, 0, 0}; 0847 auto cleanup = qScopeGuard([this, &handles]() { 0848 for (int i = 0; i < 4; ++i) { 0849 if (handles[i] == 0) { 0850 continue; 0851 } 0852 bool closed = false; 0853 for (int j = 0; j < i; ++j) { 0854 if (handles[i] == handles[j]) { 0855 closed = true; 0856 break; 0857 } 0858 } 0859 if (closed) { 0860 continue; 0861 } 0862 drmCloseBufferHandle(m_fd, handles[i]); 0863 } 0864 }); 0865 for (int i = 0; i < attributes->planeCount; ++i) { 0866 if (drmPrimeFDToHandle(m_fd, attributes->fd[i].get(), &handles[i]) != 0) { 0867 qCWarning(KWIN_DRM) << "drmPrimeFDToHandle() failed"; 0868 return nullptr; 0869 } 0870 } 0871 0872 uint32_t framebufferId = 0; 0873 int ret; 0874 if (addFB2ModifiersSupported() && attributes->modifier != DRM_FORMAT_MOD_INVALID) { 0875 uint64_t modifier[4] = {0, 0, 0, 0}; 0876 for (int i = 0; i < attributes->planeCount; ++i) { 0877 modifier[i] = attributes->modifier; 0878 } 0879 ret = drmModeAddFB2WithModifiers(m_fd, 0880 attributes->width, 0881 attributes->height, 0882 attributes->format, 0883 handles, 0884 attributes->pitch.data(), 0885 attributes->offset.data(), 0886 modifier, 0887 &framebufferId, 0888 DRM_MODE_FB_MODIFIERS); 0889 } else { 0890 ret = drmModeAddFB2(m_fd, 0891 attributes->width, 0892 attributes->height, 0893 attributes->format, 0894 handles, 0895 attributes->pitch.data(), 0896 attributes->offset.data(), 0897 &framebufferId, 0898 0); 0899 if (ret == EOPNOTSUPP && attributes->planeCount == 1) { 0900 ret = drmModeAddFB(m_fd, 0901 attributes->width, 0902 attributes->height, 0903 24, 32, 0904 attributes->pitch[0], 0905 handles[0], 0906 &framebufferId); 0907 } 0908 } 0909 0910 if (ret != 0) { 0911 return nullptr; 0912 } 0913 0914 return std::make_shared<DrmFramebuffer>(this, framebufferId, buffer, std::move(readFence)); 0915 } 0916 0917 DrmLease::DrmLease(DrmGpu *gpu, FileDescriptor &&fd, uint32_t lesseeId, const QList<DrmOutput *> &outputs) 0918 : m_gpu(gpu) 0919 , m_fd(std::move(fd)) 0920 , m_lesseeId(lesseeId) 0921 , m_outputs(outputs) 0922 { 0923 for (const auto output : m_outputs) { 0924 output->leased(this); 0925 } 0926 } 0927 0928 DrmLease::~DrmLease() 0929 { 0930 qCDebug(KWIN_DRM, "Revoking lease with leaseID %d", m_lesseeId); 0931 drmModeRevokeLease(m_gpu->fd(), m_lesseeId); 0932 for (const auto &output : m_outputs) { 0933 output->leaseEnded(); 0934 } 0935 } 0936 0937 FileDescriptor &DrmLease::fd() 0938 { 0939 return m_fd; 0940 } 0941 0942 uint32_t DrmLease::lesseeId() const 0943 { 0944 return m_lesseeId; 0945 } 0946 } 0947 0948 #include "moc_drm_gpu.cpp"