/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2020 Xaver Hugl SPDX-License-Identifier: GPL-2.0-or-later */ #include "drm_gpu.h" #include "drm_backend.h" #include "drm_output.h" #include "drm_object_connector.h" #include "drm_object_crtc.h" #include "abstract_egl_backend.h" #include "logging.h" #include "logind.h" #if HAVE_GBM #include "egl_gbm_backend.h" #include #include "gbm_dmabuf.h" #endif // system #include #include #include // drm #include #include #include namespace KWin { DrmGpu::DrmGpu(DrmBackend *backend, QByteArray devNode, int fd, int drmId) : m_backend(backend), m_devNode(devNode), m_fd(fd), m_drmId(drmId), m_atomicModeSetting(false), m_gbmDevice(nullptr) { uint64_t capability = 0; if (drmGetCap(fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) { m_cursorSize.setWidth(capability); } else { m_cursorSize.setWidth(64); } if (drmGetCap(fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) { m_cursorSize.setHeight(capability); } else { m_cursorSize.setHeight(64); } int ret = drmGetCap(fd, DRM_CAP_TIMESTAMP_MONOTONIC, &capability); if (ret == 0 && capability == 1) { m_presentationClock = CLOCK_MONOTONIC; } else { m_presentationClock = CLOCK_REALTIME; } // find out if this GPU is using the NVidia proprietary driver DrmScopedPointer version(drmGetVersion(fd)); m_useEglStreams = strstr(version->name, "nvidia-drm"); m_deleteBufferAfterPageFlip = !m_useEglStreams; m_socketNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); connect(m_socketNotifier, &QSocketNotifier::activated, this, &DrmGpu::dispatchEvents); } DrmGpu::~DrmGpu() { waitIdle(); if (m_eglDisplay != EGL_NO_DISPLAY) { eglTerminate(m_eglDisplay); } #if HAVE_GBM if (m_gbmDevice) { gbm_device_destroy(m_gbmDevice); } #endif qDeleteAll(m_crtcs); qDeleteAll(m_connectors); qDeleteAll(m_planes); delete m_socketNotifier; LogindIntegration::self()->releaseDevice(m_fd); } clockid_t DrmGpu::presentationClock() const { return m_presentationClock; } void DrmGpu::tryAMS() { m_atomicModeSetting = false; if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) == 0) { m_atomicModeSetting = true; DrmScopedPointer planeResources(drmModeGetPlaneResources(m_fd)); if (!planeResources) { qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode on GPU " << m_devNode; m_atomicModeSetting = false; return; } qCDebug(KWIN_DRM) << "Using Atomic Mode Setting on gpu" << m_devNode; qCDebug(KWIN_DRM) << "Number of planes on GPU" << m_devNode << ":" << planeResources->count_planes; // create the plane objects for (unsigned int i = 0; i < planeResources->count_planes; ++i) { DrmScopedPointer kplane(drmModeGetPlane(m_fd, planeResources->planes[i])); DrmPlane *p = new DrmPlane(kplane->plane_id, m_fd); if (p->init()) { m_planes << p; } else { delete p; } } if (m_planes.isEmpty()) { qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode on GPU " << m_devNode; m_atomicModeSetting = false; } m_unusedPlanes = m_planes; } else { qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode on GPU" << m_devNode; } } bool DrmGpu::updateOutputs() { auto oldConnectors = m_connectors; auto oldCrtcs = m_crtcs; DrmScopedPointer resources(drmModeGetResources(m_fd)); if (!resources) { qCWarning(KWIN_DRM) << "drmModeGetResources failed"; return false; } for (int i = 0; i < resources->count_connectors; ++i) { const uint32_t currentConnector = resources->connectors[i]; auto it = std::find_if(m_connectors.constBegin(), m_connectors.constEnd(), [currentConnector] (DrmConnector *c) { return c->id() == currentConnector; }); if (it == m_connectors.constEnd()) { auto c = new DrmConnector(currentConnector, m_fd); if (!c->init()) { delete c; continue; } if (c->isNonDesktop()) { delete c; continue; } if (!c->isConnected()) { delete c; continue; } m_connectors << c; } else { oldConnectors.removeOne(*it); } } for (int i = 0; i < resources->count_crtcs; ++i) { const uint32_t currentCrtc = resources->crtcs[i]; auto it = std::find_if(m_crtcs.constBegin(), m_crtcs.constEnd(), [currentCrtc] (DrmCrtc *c) { return c->id() == currentCrtc; }); if (it == m_crtcs.constEnd()) { auto c = new DrmCrtc(currentCrtc, m_backend, this, i); if (!c->init()) { delete c; continue; } m_crtcs << c; } else { oldCrtcs.removeOne(*it); } } for (auto c : qAsConst(oldConnectors)) { m_connectors.removeOne(c); } for (auto c : qAsConst(oldCrtcs)) { m_crtcs.removeOne(c); } QVector connectedOutputs; QVector pendingConnectors; // split up connected connectors in already or not yet assigned ones for (DrmConnector *con : qAsConst(m_connectors)) { if (!con->isConnected()) { continue; } if (DrmOutput *o = findOutput(con->id())) { connectedOutputs << o; } else { pendingConnectors << con; } } // check for outputs which got removed QVector removedOutputs; auto it = m_outputs.begin(); while (it != m_outputs.end()) { if (connectedOutputs.contains(*it)) { it++; continue; } DrmOutput *removed = *it; it = m_outputs.erase(it); removedOutputs.append(removed); } for (DrmConnector *con : qAsConst(pendingConnectors)) { DrmScopedPointer connector(drmModeGetConnector(m_fd, con->id())); if (!connector) { continue; } if (connector->count_modes == 0) { continue; } bool outputDone = false; QVector encoders = con->encoders(); for (auto encId : qAsConst(encoders)) { DrmScopedPointer encoder(drmModeGetEncoder(m_fd, encId)); if (!encoder) { continue; } for (DrmCrtc *crtc : qAsConst(m_crtcs)) { if (!(encoder->possible_crtcs & (1 << crtc->resIndex()))) { continue; } // check if crtc isn't used yet -- currently we don't allow multiple outputs on one crtc (cloned mode) auto it = std::find_if(connectedOutputs.constBegin(), connectedOutputs.constEnd(), [crtc] (DrmOutput *o) { return o->m_crtc == crtc; } ); if (it != connectedOutputs.constEnd()) { continue; } // we found a suitable encoder+crtc // TODO: we could avoid these lib drm calls if we store all struct data in DrmCrtc and DrmConnector in the beginning DrmScopedPointer modeCrtc(drmModeGetCrtc(m_fd, crtc->id())); if (!modeCrtc) { continue; } DrmOutput *output = new DrmOutput(this->m_backend, this); output->m_conn = con; output->m_crtc = crtc; output->m_mode = connector->modes[0]; output->m_primaryPlane = getCompatiblePlane(DrmPlane::TypeIndex::Primary, crtc); output->m_cursorPlane = getCompatiblePlane(DrmPlane::TypeIndex::Cursor, crtc); qCDebug(KWIN_DRM) << "For new output use mode " << output->m_mode.name << output->m_mode.hdisplay << output->m_mode.vdisplay; if (!output->init(connector.data())) { qCWarning(KWIN_DRM) << "Failed to create output for connector " << con->id(); delete output; continue; } if (!output->initCursor(m_cursorSize)) { m_backend->setSoftwareCursorForced(true); } qCDebug(KWIN_DRM) << "Found new output with uuid" << output->uuid() << "on gpu" << m_devNode; connectedOutputs << output; emit outputAdded(output); outputDone = true; break; } if (outputDone) { break; } } } std::sort(connectedOutputs.begin(), connectedOutputs.end(), [] (DrmOutput *a, DrmOutput *b) { return a->m_conn->id() < b->m_conn->id(); }); m_outputs = connectedOutputs; for(DrmOutput *removedOutput : removedOutputs) { emit outputRemoved(removedOutput); removedOutput->teardown(); removedOutput->m_crtc = nullptr; m_connectors.removeOne(removedOutput->m_conn); delete removedOutput->m_conn; removedOutput->m_conn = nullptr; if (removedOutput->m_primaryPlane) { m_unusedPlanes << removedOutput->m_primaryPlane; } } qDeleteAll(oldConnectors); qDeleteAll(oldCrtcs); return true; } DrmOutput *DrmGpu::findOutput(quint32 connector) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [connector] (DrmOutput *o) { return o->m_conn->id() == connector; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } DrmPlane *DrmGpu::getCompatiblePlane(DrmPlane::TypeIndex typeIndex, DrmCrtc *crtc) { for (auto plane : m_unusedPlanes) { if (plane->type() != typeIndex) { continue; } if (plane->isCrtcSupported(crtc->resIndex())) { m_unusedPlanes.removeOne(plane); return plane; } } return nullptr; } void DrmGpu::dispatchEvents() { if (!LogindIntegration::self()->isActiveSession()) { return; } drmEventContext context = {}; context.version = 2; context.page_flip_handler = DrmBackend::pageFlipHandler; drmHandleEvent(m_fd, &context); } void DrmGpu::waitIdle() { m_socketNotifier->setEnabled(false); while (true) { const bool idle = std::all_of(m_outputs.constBegin(), m_outputs.constEnd(), [](DrmOutput *output){ return !output->m_pageFlipPending; }); if (idle) { break; } fd_set set; FD_ZERO(&set); FD_SET(m_fd, &set); timeval timeout; timeout.tv_sec = 30; timeout.tv_usec = 0; const int descriptorCount = select(m_fd + 1, &set, nullptr, nullptr, &timeout); if (descriptorCount < 0) { qCWarning(KWIN_DRM) << "select() in DrmGpu::waitIdle failed:" << strerror(errno); break; } else if (FD_ISSET(m_fd, &set)) { dispatchEvents(); } else { qCWarning(KWIN_DRM) << "No drm events for gpu" << m_devNode << "within last 30 seconds"; break; } }; m_socketNotifier->setEnabled(true); } }