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 &timestamp)
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 &timestamp)
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"