diff --git a/CMakeLists.txt b/CMakeLists.txt index a3a07da9..98537ea8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,9 @@ if(FEATURE_aurora_qpa) add_subdirectory(src/plugins/platforms/eglfs) # add_subdirectory(src/platformsupport/libinput) endif() +if(FEATURE_aurora_deviceintegration_drm) + add_subdirectory(src/plugins/deviceintegration/drm) +endif() if(FEATURE_aurora_deviceintegration_wayland) add_subdirectory(src/plugins/deviceintegration/wayland) endif() diff --git a/features.cmake b/features.cmake index 549334ab..5f64de98 100644 --- a/features.cmake +++ b/features.cmake @@ -391,6 +391,35 @@ endif() add_feature_info("Aurora::VulkanServerBuffer" FEATURE_aurora_vulkan_server_buffer "Build Wayland compositor with Vulkan-based server buffer integration") set(LIRI_FEATURE_aurora_vulkan_server_buffer "$") +# Device Integration: drm +option(FEATURE_aurora_deviceintegration_drm "Device Integration: drm" ON) +if(FEATURE_aurora_deviceintegration_drm) + find_package(EGL QUIET) + + if(NOT TARGET EGL::EGL) + message(WARNING "You need EGL for Aurora::DeviceIntegration::DRM") + set(FEATURE_aurora_deviceintegration_drm OFF) + endif() + if(NOT TARGET PkgConfig::Libudev) + message(WARNING "You need udev for Aurora::DeviceIntegration::DRM") + set(FEATURE_aurora_deviceintegration_drm OFF) + endif() + if(NOT TARGET PkgConfig::Libinput) + message(WARNING "You need libinput for Aurora::DeviceIntegration::DRM") + set(FEATURE_aurora_deviceintegration_drm OFF) + endif() + if(NOT TARGET PkgConfig::Libdrm) + message(WARNING "You need libdrm for Aurora::DeviceIntegration::DRM") + set(FEATURE_aurora_deviceintegration_drm OFF) + endif() + if(NOT TARGET PkgConfig::Gbm) + message(WARNING "You need gbm for Aurora::DeviceIntegration::DRM") + set(FEATURE_aurora_deviceintegration_drm OFF) + endif() +endif() +add_feature_info("Aurora::DeviceIntegration::DRM" FEATURE_aurora_deviceintegration_drm "Build DRM/KMS device integration") +set(LIRI_FEATURE_aurora_deviceintegration_drm "$") + # Device Integration: wayland option(FEATURE_aurora_deviceintegration_wayland "Device Integration: wayland" ON) if(FEATURE_aurora_deviceintegration_wayland) diff --git a/src/plugins/deviceintegration/drm/CMakeLists.txt b/src/plugins/deviceintegration/drm/CMakeLists.txt new file mode 100644 index 00000000..506c46fd --- /dev/null +++ b/src/plugins/deviceintegration/drm/CMakeLists.txt @@ -0,0 +1,54 @@ +include(ECMQtDeclareLoggingCategory) +ecm_qt_declare_logging_category( + AuroraDeviceIntegrationDrm_SOURCES + HEADER "drmloggingcategories.h" + IDENTIFIER "Aurora::Platform::gLcDrm" + CATEGORY_NAME "aurora.platform.drm" + DEFAULT_SEVERITY "Info" + DESCRIPTION "Aurora device integration for DRM/KMS" +) + +qt6_add_plugin(AuroraDeviceIntegrationDrm + SHARED + CLASS_NAME DrmIntegrationPlugin + MANUAL_FINALIZATION + drm.json + drmbackend.cpp drmbackend.h + drmblob.cpp drmblob.h + drmcrtc.cpp drmcrtc.h + drmcursor.cpp drmcursor.h + drmgpu.cpp drmgpu.h + drmintegration.cpp drmintegration.h + drmintegrationplugin.cpp drmintegrationplugin.h + drmobject.cpp drmobject.h + drmoutput.cpp drmoutput.h + drmplane.cpp drmplane.h + drmpointer.h + drmproperty.cpp drmproperty.h + drmwindow.cpp drmwindow.h + ${AuroraDeviceIntegrationDrm_SOURCES} +) + +set_target_properties(AuroraDeviceIntegrationDrm + PROPERTIES OUTPUT_NAME drm +) + +target_link_libraries(AuroraDeviceIntegrationDrm + PUBLIC + Qt6::Core + Qt6::Gui + Liri::AuroraCore + Liri::AuroraPlatform + EGL::EGL + PkgConfig::Libdrm + PkgConfig::Gbm + PRIVATE + Liri::AuroraPlatformPrivate +) + +qt6_finalize_target(AuroraDeviceIntegrationDrm) + +install( + TARGETS AuroraDeviceIntegrationDrm + DESTINATION ${KDE_INSTALL_PLUGINDIR}/aurora/deviceintegration +) diff --git a/src/plugins/deviceintegration/drm/drm.json b/src/plugins/deviceintegration/drm/drm.json new file mode 100644 index 00000000..735ca9ae --- /dev/null +++ b/src/plugins/deviceintegration/drm/drm.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "drm" ] +} diff --git a/src/plugins/deviceintegration/drm/drmbackend.cpp b/src/plugins/deviceintegration/drm/drmbackend.cpp new file mode 100644 index 00000000..45c684ed --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmbackend.cpp @@ -0,0 +1,227 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "drmbackend.h" +#include "drmgpu.h" +#include "drmloggingcategories.h" +#include "drmoutput.h" + +#include + +#include +#include +#include +#include + +#ifndef EGL_EXT_platform_base +typedef EGLDisplay(EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum platform, + void *native_display, + const EGLint *attrib_list); +#endif + +#ifndef EGL_PLATFORM_GBM_KHR +# define EGL_PLATFORM_GBM_KHR 0x31D7 +#endif + +namespace Aurora { + +namespace Platform { + +Q_GLOBAL_STATIC(DrmBackend, gDrmBackend) + +DrmBackend::DrmBackend(QObject *parent) + : QObject(parent) + , m_session(Session::create(Session::Type::Logind, this)) + , m_udev(new Aurora::PlatformSupport::Udev()) +{ +} + +DrmBackend::~DrmBackend() +{ + if (m_udev) { + delete m_udev; + m_udev = nullptr; + } +} + +Session *DrmBackend::session() const +{ + return m_session; +} + +EGLDisplay DrmBackend::eglDisplay() const +{ + return m_eglDisplay; +} + +EGLNativeDisplayType DrmBackend::platformDisplay() const +{ + if (m_gpu) + return reinterpret_cast(m_gpu->gbmDevice()); + return EGL_CAST(EGLNativeDisplayType, 0); +} + +bool DrmBackend::isAtomicEnabled() const +{ + return m_enableAtomic; +} + +DrmGpu *DrmBackend::primaryGpu() const +{ + return m_gpu; +} + +void DrmBackend::initialize() +{ + if (m_initialized) + return; + + m_initialized = true; + + // Session + connect(m_session, &Session::devicePaused, this, [this](dev_t deviceId) { + if (auto *gpu = findGpu(deviceId)) + gpu->setActive(false); + }); + connect(m_session, &Session::deviceResumed, this, [this](dev_t deviceId) { + if (auto *gpu = findGpu(deviceId)) + gpu->setActive(true); + }); + + // Find all GPUs + Aurora::PlatformSupport::UdevEnumerate enumerate( + Aurora::PlatformSupport::UdevDevice::PrimaryVideoDevice + | Aurora::PlatformSupport::UdevDevice::GenericVideoDevice, + m_udev); + auto udevDevices = enumerate.scan(); + if (Q_UNLIKELY(udevDevices.isEmpty())) + qFatal("Could not find DRM device!"); + + // Create GPUs + if (!udevDevices.isEmpty()) { + qCDebug(gLcDrm, "Found the following video devices for the \"%s\" seat:", + qPrintable(m_session->seat())); + for (auto *udevDevice : qAsConst(udevDevices)) { + if (udevDevice->seat() == m_session->seat()) { + const auto path = udevDevice->deviceNode(); + qCDebug(gLcDrm) << '\t' << path.toLocal8Bit().constData(); + addGpu(path); + } + } + } + if (Q_UNLIKELY(m_gpus.isEmpty())) + qFatal("No suitable DRM device have been found"); + + // Select the first one + m_gpu = m_gpus.first(); + + // Create EGL display + createDisplay(); +} + +void DrmBackend::destroy() +{ +} + +DrmBackend *DrmBackend::instance() +{ + return gDrmBackend(); +} + +DrmGpu *DrmBackend::addGpu(const QString &path) +{ + // Open the DRM device + int fd = m_session->openRestricted(path); + if (fd < 0) { + qCWarning(gLcDrm) << "Failed to open DRM device" << path; + return nullptr; + } + + // Make a simpel DRM call to check if the device is usable + drmModeResPtr resources = drmModeGetResources(fd); + if (!resources) { + qCDebug(gLcDrm) << "Skipping KMS incapable DRM device" << path; + m_session->closeRestricted(fd); + return nullptr; + } + drmModeFreeResources(resources); + + struct stat sbuf; + if (fstat(fd, &sbuf) < 0) { + qCDebug(gLcDrm, "Failed to fstat \"%s\": %s", qPrintable(path), strerror(errno)); + m_session->closeRestricted(fd); + return nullptr; + } + + qCInfo(gLcDrm) << "Adding DRM device:" << path; + + auto *gpu = new DrmGpu(path, fd, sbuf.st_rdev); + m_gpus.append(gpu); + emit gpuAdded(gpu); + return gpu; +} + +DrmGpu *DrmBackend::findGpu(dev_t deviceId) const +{ + auto it = std::find_if(m_gpus.begin(), m_gpus.end(), + [deviceId](const auto *gpu) { return gpu->deviceId() == deviceId; }); + return it == m_gpus.end() ? nullptr : *it; +} + +void DrmBackend::createDisplay() +{ + EGLNativeDisplayType nativeDisplay = platformDisplay(); + + PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplay = nullptr; + const char *extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (extensions + && (strstr(extensions, "EGL_KHR_platform_gbm") + || strstr(extensions, "EGL_MESA_platform_gbm"))) { + getPlatformDisplay = reinterpret_cast( + eglGetProcAddress("eglGetPlatformDisplayEXT")); + } + + if (getPlatformDisplay) { + m_eglDisplay = getPlatformDisplay(EGL_PLATFORM_GBM_KHR, nativeDisplay, nullptr); + } else { + qCDebug(gLcDrm, "No eglGetPlatformDisplay for GBM, falling back to eglGetDisplay"); + m_eglDisplay = eglGetDisplay(nativeDisplay); + } +} + +void DrmBackend::updateOutputs() +{ + for (auto it = m_gpus.begin(); it != m_gpus.end(); ++it) { + auto *gpu = (*it); + if (gpu->isRemoved()) + gpu->removeOutputs(); + else + gpu->updateOutputs(); + } + + for (auto it = m_gpus.begin(); it != m_gpus.end();) { + auto *gpu = (*it); + + if (gpu->isRemoved() || (gpu != m_gpu && gpu->hasDrmOutputs())) { + qCDebug(gLcDrm, "Removing GPU \"%s\"", qPrintable(gpu->deviceNode())); + it = m_gpus.erase(it); + emit gpuRemoved(gpu); + } else { + it++; + } + } +} + +void DrmBackend::addOutput(DrmOutput *output) +{ +} + +void DrmBackend::removeOutput(DrmOutput *output) +{ +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmbackend.h b/src/plugins/deviceintegration/drm/drmbackend.h new file mode 100644 index 00000000..94950bb5 --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmbackend.h @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include +#include + +#include + +namespace Aurora { + +namespace Platform { + +class DrmGpu; +class DrmOutput; + +class DrmBackend : public QObject +{ + Q_OBJECT +public: + explicit DrmBackend(QObject *parent = nullptr); + ~DrmBackend(); + + Session *session() const; + + EGLDisplay eglDisplay() const; + EGLNativeDisplayType platformDisplay() const; + + bool isAtomicEnabled() const; + + DrmGpu *primaryGpu() const; + + void initialize(); + void destroy(); + + static DrmBackend *instance(); + +signals: + void gpuAdded(DrmGpu *gpu); + void gpuRemoved(DrmGpu *gpu); + +private: + bool m_initialized = false; + Session *m_session = nullptr; + Aurora::PlatformSupport::Udev *m_udev = nullptr; + DrmGpu *m_gpu = nullptr; + QList m_gpus; + EGLDisplay m_eglDisplay = EGL_NO_DISPLAY; + bool m_enableAtomic = true; + + DrmGpu *findGpu(dev_t deviceId) const; + DrmGpu *addGpu(const QString &path); + + void createDisplay(); + void updateOutputs(); + +private slots: + void addOutput(DrmOutput *output); + void removeOutput(DrmOutput *output); + void updateOutputs(); +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmblob.cpp b/src/plugins/deviceintegration/drm/drmblob.cpp new file mode 100644 index 00000000..b7e70506 --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmblob.cpp @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2023 Xaver Hugl +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "drmblob.h" +#include "drmgpu.h" + +namespace Aurora { + +namespace Platform { + +DrmBlob::DrmBlob(DrmGpu *gpu, uint32_t blobId) + : m_gpu(gpu) + , m_blobId(blobId) +{ +} + +DrmBlob::~DrmBlob() +{ + if (m_blobId) + drmModeDestroyPropertyBlob(m_gpu->fd(), m_blobId); +} + +uint32_t DrmBlob::blobId() const +{ + return m_blobId; +} + +std::shared_ptr DrmBlob::create(DrmGpu *gpu, const void *data, uint32_t dataSize) +{ + uint32_t id = 0; + if (drmModeCreatePropertyBlob(gpu->fd(), data, dataSize, &id) == 0) + return std::make_shared(gpu, id); + else + return nullptr; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmblob.h b/src/plugins/deviceintegration/drm/drmblob.h new file mode 100644 index 00000000..5cdcfe05 --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmblob.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2023 Xaver Hugl +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace Aurora { + +namespace Platform { + +class DrmGpu; + +class DrmBlob +{ +public: + explicit DrmBlob(DrmGpu *gpu, uint32_t blobId); + ~DrmBlob(); + + uint32_t blobId() const; + + static std::shared_ptr create(DrmGpu *gpu, const void *data, uint32_t dataSize); + +protected: + DrmGpu *const m_gpu; + const uint32_t m_blobId; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmcrtc.cpp b/src/plugins/deviceintegration/drm/drmcrtc.cpp new file mode 100644 index 00000000..56fb820b --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmcrtc.cpp @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2023 Xaver Hugl +// SPDX-License-Identifier: GPL-2.0-or-later + +//#include "drmbuffer.h" +//#include "drmcommit.h" +#include "drmcrtc.h" +#include "drmgpu.h" + +namespace Aurora { + +namespace Platform { + +DrmCrtc::DrmCrtc(DrmGpu *gpu, uint32_t crtcId, int pipeIndex, DrmPlane *primaryPlane, DrmPlane *cursorPlane) + : DrmObject(gpu, crtcId, DRM_MODE_OBJECT_CRTC) + , modeId(this, QByteArrayLiteral("MODE_ID")) + , active(this, QByteArrayLiteral("ACTIVE")) + , vrrEnabled(this, QByteArrayLiteral("VRR_ENABLED")) + , gammaLut(this, QByteArrayLiteral("GAMMA_LUT")) + , gammaLutSize(this, QByteArrayLiteral("GAMMA_LUT_SIZE")) + , ctm(this, QByteArrayLiteral("CTM")) + , degammaLut(this, QByteArrayLiteral("DEGAMMA_LUT")) + , degammaLutSize(this, QByteArrayLiteral("DEGAMMA_LUT_SIZE")) + , m_crtc(drmModeGetCrtc(gpu->fd(), crtcId)) + , m_pipeIndex(pipeIndex) + , m_primaryPlane(primaryPlane) + , m_cursorPlane(cursorPlane) +{ +} + +bool DrmCrtc::updateProperties() +{ + if (!m_crtc) + return false; + + DrmPropertyList props = queryProperties(); + modeId.update(props); + active.update(props); + vrrEnabled.update(props); + gammaLut.update(props); + gammaLutSize.update(props); + ctm.update(props); + degammaLut.update(props); + degammaLutSize.update(props); + + return !gpu()->atomicModeSetting() || (modeId.isValid() && active.isValid()); +} + +drmModeModeInfo DrmCrtc::queryCurrentMode() +{ + m_crtc.reset(drmModeGetCrtc(gpu()->fd(), id())); + return m_crtc->mode; +} + +int DrmCrtc::pipeIndex() const +{ + return m_pipeIndex; +} + +std::shared_ptr DrmCrtc::current() const +{ + return m_currentBuffer; +} + +void DrmCrtc::setCurrent(const std::shared_ptr &buffer) +{ + m_currentBuffer = buffer; +} + +int DrmCrtc::gammaRampSize() const +{ + if (gpu()->atomicModeSetting()) { + // limit atomic gamma ramp to 4096 to work around https://gitlab.freedesktop.org/drm/intel/-/issues/3916 + if (gammaLutSize.isValid() && gammaLutSize.value() <= 4096) + return gammaLutSize.value(); + } + return m_crtc->gamma_size; +} + +DrmPlane *DrmCrtc::primaryPlane() const +{ + return m_primaryPlane; +} + +DrmPlane *DrmCrtc::cursorPlane() const +{ + return m_cursorPlane; +} + +void DrmCrtc::disable(DrmAtomicCommit *commit) +{ + commit->addProperty(active, 0); + commit->addProperty(modeId, 0); +} + +void DrmCrtc::releaseCurrentBuffer() +{ + if (m_currentBuffer) + m_currentBuffer->releaseBuffer(); +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmcrtc.h b/src/plugins/deviceintegration/drm/drmcrtc.h new file mode 100644 index 00000000..66f3214b --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmcrtc.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2023 Xaver Hugl +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "drmobject.h" + +namespace Aurora { + +namespace Platform { + +class DrmFramebuffer; +class DrmGpu; +class DrmPlane; + +class DrmCrtc : public DrmObject +{ +public: + DrmCrtc(DrmGpu *gpu, uint32_t crtcId, int pipeIndex, DrmPlane *primaryPlane, DrmPlane *cursorPlane); + + void disable(DrmAtomicCommit *commit) override; + bool updateProperties() override; + + int pipeIndex() const; + int gammaRampSize() const; + DrmPlane *primaryPlane() const; + DrmPlane *cursorPlane() const; + drmModeModeInfo queryCurrentMode(); + + std::shared_ptr current() const; + void setCurrent(const std::shared_ptr &buffer); + void releaseCurrentBuffer(); + + DrmProperty modeId; + DrmProperty active; + DrmProperty vrrEnabled; + DrmProperty gammaLut; + DrmProperty gammaLutSize; + DrmProperty ctm; + DrmProperty degammaLut; + DrmProperty degammaLutSize; + +private: + DrmUniquePtr m_crtc; + std::shared_ptr m_currentBuffer; + int m_pipeIndex; + DrmPlane *m_primaryPlane; + DrmPlane *m_cursorPlane; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmcursor.cpp b/src/plugins/deviceintegration/drm/drmcursor.cpp new file mode 100644 index 00000000..85a7c624 --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmcursor.cpp @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "drmbackend.h" +#include "drmcursor.h" +#include "drmgpu.h" +#include "drmloggingcategories.h" + +#include +#include +#include + +#ifndef DRM_CAP_CURSOR_WIDTH +# define DRM_CAP_CURSOR_WIDTH 0x8 +#endif + +#ifndef DRM_CAP_CURSOR_HEIGHT +# define DRM_CAP_CURSOR_HEIGHT 0x9 +#endif + +namespace Aurora { + +namespace Platform { + +DrmCursor::DrmCursor(QObject *parent) + : QObject(parent) +{ + const auto fd = DrmBackend::instance()->primaryGpu()->fd(); + auto *gbmDevice = DrmBackend::instance()->primaryGpu()->gbmDevice(); + + uint64_t width, height; + if ((drmGetCap(fd, DRM_CAP_CURSOR_WIDTH, &width) == 0) + && (drmGetCap(fd, DRM_CAP_CURSOR_HEIGHT, &height) == 0)) { + m_cursorSize.setWidth(width); + m_cursorSize.setHeight(height); + } + + m_gbmBo = gbm_bo_create(gbmDevice, m_cursorSize.width(), m_cursorSize.height(), + GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR_64X64 | GBM_BO_USE_WRITE); + if (!m_gbmBo) + qCWarning(gLcDrm) << "Failed to create buffer for cursor"; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmcursor.h b/src/plugins/deviceintegration/drm/drmcursor.h new file mode 100644 index 00000000..b2ab057e --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmcursor.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +struct gbm_bo; + +namespace Aurora { + +namespace Platform { + +class DrmCursor : public QObject +{ +public: + explicit DrmCursor(QObject *parent = nullptr); + +private: + QSize m_cursorSize = QSize(64, 64); + gbm_bo *m_gbmBo; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmgpu.cpp b/src/plugins/deviceintegration/drm/drmgpu.cpp new file mode 100644 index 00000000..4922d4f0 --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmgpu.cpp @@ -0,0 +1,917 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2020 Xaver Hugl +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "drmbackend.h" +#include "drmgpu.h" +#include "drmloggingcategories.h" + +#include + +namespace Aurora { + +namespace Platform { + +DrmGpu::DrmGpu(DrmBackend *backend, const QString &devNode, int fd, dev_t deviceId) + : m_fd(fd) + , m_deviceId(deviceId) + , m_devNode(devNode) + , m_atomicModeSetting(false) + , m_gbmDevice(nullptr) + , m_platform(backend) +{ + 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; + } + + m_addFB2ModifiersSupported = drmGetCap(fd, DRM_CAP_ADDFB2_MODIFIERS, &capability) == 0 && capability == 1; + qCDebug(KWIN_DRM) << "drmModeAddFB2WithModifiers is" << (m_addFB2ModifiersSupported ? "supported" : "not supported") << "on GPU" << m_devNode; + + // find out what driver this kms device is using + DrmUniquePtr version(drmGetVersion(fd)); + m_isI915 = strstr(version->name, "i915"); + m_isNVidia = strstr(version->name, "nvidia-drm"); + m_isVirtualMachine = strstr(version->name, "virtio") || strstr(version->name, "qxl") + || strstr(version->name, "vmwgfx") || strstr(version->name, "vboxvideo"); + + // Reopen the drm node to create a new GEM handle namespace. + m_gbmFd = FileDescriptor{open(devNode.toLocal8Bit(), O_RDWR | O_CLOEXEC)}; + if (m_gbmFd.isValid()) { + drm_magic_t magic; + drmGetMagic(m_gbmFd.get(), &magic); + drmAuthMagic(m_fd, magic); + m_gbmDevice = gbm_create_device(m_gbmFd.get()); + if (!m_gbmDevice) { + qCCritical(KWIN_DRM) << "gbm_create_device() failed"; + } else { + m_allocator = std::make_unique(m_gbmDevice); + } + } + + m_socketNotifier = std::make_unique(fd, QSocketNotifier::Read); + connect(m_socketNotifier.get(), &QSocketNotifier::activated, this, &DrmGpu::dispatchEvents); + + initDrmResources(); + + if (m_atomicModeSetting == false) { + // only supported with legacy + m_asyncPageflipSupported = drmGetCap(fd, DRM_CAP_ASYNC_PAGE_FLIP, &capability) == 0 && capability == 1; + } +} + +DrmGpu::~DrmGpu() +{ + removeOutputs(); + m_eglDisplay.reset(); + m_crtcs.clear(); + m_connectors.clear(); + m_planes.clear(); + m_socketNotifier.reset(); + m_allocator.reset(); + if (m_gbmDevice) { + gbm_device_destroy(m_gbmDevice); + } + m_gbmFd = FileDescriptor{}; + m_platform->session()->closeRestricted(m_fd); +} + +FileDescriptor DrmGpu::createNonMasterFd() const +{ + char *path = drmGetDeviceNameFromFd2(m_fd); + FileDescriptor fd{open(path, O_RDWR | O_CLOEXEC)}; + if (!fd.isValid()) { + qCWarning(KWIN_DRM) << "Could not open DRM fd for leasing!" << strerror(errno); + } else { + if (drmIsMaster(fd.get())) { + if (drmDropMaster(fd.get()) != 0) { + qCWarning(KWIN_DRM) << "Could not create a non-master DRM fd for leasing!" << strerror(errno); + return FileDescriptor{}; + } + } + } + return fd; +} + +clockid_t DrmGpu::presentationClock() const +{ + return m_presentationClock; +} + +void DrmGpu::initDrmResources() +{ + // try atomic mode setting + bool isEnvVarSet = false; + const bool supportsVmCursorHotspot = drmSetClientCap(m_fd, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT, 1) == 0; + const bool noAMS = qEnvironmentVariableIntValue("KWIN_DRM_NO_AMS", &isEnvVarSet) != 0 && isEnvVarSet; + if (m_isVirtualMachine && !supportsVmCursorHotspot && !isEnvVarSet) { + qCWarning(KWIN_DRM, "Atomic Mode Setting disabled on GPU %s because of cursor offset issues in virtual machines", qPrintable(m_devNode)); + } else if (noAMS) { + qCWarning(KWIN_DRM) << "Atomic Mode Setting requested off via environment variable. Using legacy mode on GPU" << m_devNode; + } else if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) != 0) { + qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode on GPU" << m_devNode; + } else { + DrmUniquePtr planeResources(drmModeGetPlaneResources(m_fd)); + if (planeResources) { + 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) { + DrmUniquePtr kplane(drmModeGetPlane(m_fd, planeResources->planes[i])); + auto plane = std::make_unique(this, kplane->plane_id); + if (plane->init()) { + m_allObjects << plane.get(); + m_planes.push_back(std::move(plane)); + } + } + if (m_planes.empty()) { + qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode on GPU " << m_devNode; + } + } else { + qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode on GPU " << m_devNode; + } + } + m_atomicModeSetting = !m_planes.empty(); + + DrmUniquePtr resources(drmModeGetResources(m_fd)); + if (!resources) { + qCCritical(KWIN_DRM) << "drmModeGetResources for getting CRTCs failed on GPU" << m_devNode; + return; + } + QList assignedPlanes; + for (int i = 0; i < resources->count_crtcs; ++i) { + uint32_t crtcId = resources->crtcs[i]; + QList primaryCandidates; + QList cursorCandidates; + for (const auto &plane : m_planes) { + if (plane->isCrtcSupported(i) && !assignedPlanes.contains(plane.get())) { + if (plane->type.enumValue() == DrmPlane::TypeIndex::Primary) { + primaryCandidates.push_back(plane.get()); + } else if (plane->type.enumValue() == DrmPlane::TypeIndex::Cursor) { + cursorCandidates.push_back(plane.get()); + } + } + } + if (m_atomicModeSetting && primaryCandidates.empty()) { + qCWarning(KWIN_DRM) << "Could not find a suitable primary plane for crtc" << resources->crtcs[i]; + continue; + } + const auto findBestPlane = [crtcId](const QList &list) { + // if the plane is already used with this crtc, prefer it + const auto connected = std::find_if(list.begin(), list.end(), [crtcId](DrmPlane *plane) { + return plane->crtcId.value() == crtcId; + }); + if (connected != list.end()) { + return *connected; + } + // don't take away planes from other crtcs. The kernel currently rejects such commits + const auto notconnected = std::find_if(list.begin(), list.end(), [](DrmPlane *plane) { + return plane->crtcId.value() == 0; + }); + if (notconnected != list.end()) { + return *notconnected; + } + return list.empty() ? nullptr : list.front(); + }; + DrmPlane *primary = findBestPlane(primaryCandidates); + DrmPlane *cursor = findBestPlane(cursorCandidates); + assignedPlanes.push_back(primary); + if (cursor) { + assignedPlanes.push_back(cursor); + } + auto crtc = std::make_unique(this, crtcId, i, primary, cursor); + if (!crtc->init()) { + continue; + } + m_allObjects << crtc.get(); + m_crtcs.push_back(std::move(crtc)); + } +} + +bool DrmGpu::updateOutputs() +{ + if (!m_isActive) { + return false; + } + waitIdle(); + DrmUniquePtr resources(drmModeGetResources(m_fd)); + if (!resources) { + qCWarning(KWIN_DRM) << "drmModeGetResources failed"; + return false; + } + + // In principle these things are supposed to be detected through the wayland protocol. + // In practice SteamVR doesn't always behave correctly + DrmUniquePtr lessees{drmModeListLessees(m_fd)}; + for (const auto &output : std::as_const(m_drmOutputs)) { + if (output->lease()) { + bool leaseActive = false; + for (uint i = 0; i < lessees->count; i++) { + if (lessees->lessees[i] == output->lease()->lesseeId()) { + leaseActive = true; + break; + } + } + if (!leaseActive) { + Q_EMIT output->lease()->revokeRequested(); + } + } + } + + // check for added and removed connectors + QList existing; + QList addedOutputs; + for (int i = 0; i < resources->count_connectors; ++i) { + const uint32_t currentConnector = resources->connectors[i]; + const auto it = std::find_if(m_connectors.begin(), m_connectors.end(), [currentConnector](const auto &connector) { + return connector->id() == currentConnector; + }); + if (it == m_connectors.end()) { + auto conn = std::make_shared(this, currentConnector); + if (!conn->init()) { + continue; + } + existing.push_back(conn.get()); + m_allObjects.push_back(conn.get()); + m_connectors.push_back(std::move(conn)); + } else { + (*it)->updateProperties(); + existing.push_back(it->get()); + } + } + for (auto it = m_connectors.begin(); it != m_connectors.end();) { + DrmConnector *conn = it->get(); + const auto output = findOutput(conn->id()); + const bool stillExists = existing.contains(conn); + if (!stillExists || !conn->isConnected()) { + if (output) { + removeOutput(output); + } + } else if (!output) { + qCDebug(KWIN_DRM, "New %soutput on GPU %s: %s", conn->isNonDesktop() ? "non-desktop " : "", qPrintable(m_devNode), qPrintable(conn->modelName())); + const auto pipeline = conn->pipeline(); + m_pipelines << pipeline; + auto output = new DrmOutput(*it); + m_drmOutputs << output; + addedOutputs << output; + Q_EMIT outputAdded(output); + pipeline->setLayers(m_platform->renderBackend()->createPrimaryLayer(pipeline), m_platform->renderBackend()->createCursorLayer(pipeline)); + pipeline->setActive(!conn->isNonDesktop()); + pipeline->applyPendingChanges(); + } + if (stillExists) { + it++; + } else { + m_allObjects.removeOne(it->get()); + it = m_connectors.erase(it); + } + } + + // update crtc properties + for (const auto &crtc : std::as_const(m_crtcs)) { + crtc->updateProperties(); + } + // update plane properties + for (const auto &plane : std::as_const(m_planes)) { + plane->updateProperties(); + } + DrmPipeline::Error err = testPendingConfiguration(); + if (err == DrmPipeline::Error::None) { + for (const auto &pipeline : std::as_const(m_pipelines)) { + pipeline->applyPendingChanges(); + if (pipeline->output() && !pipeline->crtc()) { + pipeline->setEnable(false); + pipeline->output()->updateEnabled(false); + } + } + } else if (err == DrmPipeline::Error::NoPermission) { + for (const auto &pipeline : std::as_const(m_pipelines)) { + pipeline->revertPendingChanges(); + } + for (const auto &output : std::as_const(addedOutputs)) { + removeOutput(output); + const auto it = std::find_if(m_connectors.begin(), m_connectors.end(), [output](const auto &conn) { + return conn.get() == output->connector(); + }); + Q_ASSERT(it != m_connectors.end()); + m_allObjects.removeOne(it->get()); + m_connectors.erase(it); + } + } else { + qCWarning(KWIN_DRM, "Failed to find a working setup for new outputs!"); + for (const auto &pipeline : std::as_const(m_pipelines)) { + pipeline->revertPendingChanges(); + } + for (const auto &output : std::as_const(addedOutputs)) { + output->updateEnabled(false); + output->pipeline()->setEnable(false); + output->pipeline()->applyPendingChanges(); + } + } + return true; +} + +void DrmGpu::removeOutputs() +{ + const auto outputs = m_drmOutputs; + for (const auto &output : outputs) { + removeOutput(output); + } + const auto virtualOutputs = m_virtualOutputs; + for (const auto &output : virtualOutputs) { + removeVirtualOutput(output); + } +} + +DrmPipeline::Error DrmGpu::checkCrtcAssignment(QList connectors, const QList &crtcs) +{ + if (connectors.isEmpty() || crtcs.isEmpty()) { + if (m_pipelines.isEmpty()) { + // nothing to do + return DrmPipeline::Error::None; + } + // remaining connectors can't be powered + for (const auto &conn : std::as_const(connectors)) { + qCWarning(KWIN_DRM) << "disabling connector" << conn->modelName() << "without a crtc"; + conn->pipeline()->setCrtc(nullptr); + } + return testPipelines(); + } + auto connector = connectors.takeFirst(); + auto pipeline = connector->pipeline(); + if (!pipeline->enabled() || !connector->isConnected()) { + // disabled pipelines don't need CRTCs + pipeline->setCrtc(nullptr); + return checkCrtcAssignment(connectors, crtcs); + } + DrmCrtc *currentCrtc = nullptr; + if (m_atomicModeSetting) { + // try the crtc that this connector is already connected to first + const uint32_t id = connector->crtcId.value(); + auto it = std::find_if(crtcs.begin(), crtcs.end(), [id](const auto &crtc) { + return id == crtc->id(); + }); + if (it != crtcs.end()) { + currentCrtc = *it; + auto crtcsLeft = crtcs; + crtcsLeft.removeOne(currentCrtc); + pipeline->setCrtc(currentCrtc); + do { + DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcsLeft); + if (err == DrmPipeline::Error::None || err == DrmPipeline::Error::NoPermission || err == DrmPipeline::Error::FramePending) { + return err; + } + } while (pipeline->pruneModifier()); + } + } + for (const auto &crtc : std::as_const(crtcs)) { + if (connector->isCrtcSupported(crtc) && crtc != currentCrtc) { + auto crtcsLeft = crtcs; + crtcsLeft.removeOne(crtc); + pipeline->setCrtc(crtc); + do { + DrmPipeline::Error err = checkCrtcAssignment(connectors, crtcsLeft); + if (err == DrmPipeline::Error::None || err == DrmPipeline::Error::NoPermission || err == DrmPipeline::Error::FramePending) { + return err; + } + } while (pipeline->pruneModifier()); + } + } + return DrmPipeline::Error::InvalidArguments; +} + +DrmPipeline::Error DrmGpu::testPendingConfiguration() +{ + QList connectors; + QList crtcs; + // only change resources that aren't currently leased away + for (const auto &conn : m_connectors) { + bool isLeased = std::any_of(m_drmOutputs.cbegin(), m_drmOutputs.cend(), [&conn](const auto output) { + return output->lease() && output->pipeline()->connector() == conn.get(); + }); + if (!isLeased) { + connectors.push_back(conn.get()); + } + } + for (const auto &crtc : m_crtcs) { + bool isLeased = std::any_of(m_drmOutputs.cbegin(), m_drmOutputs.cend(), [&crtc](const auto output) { + return output->lease() && output->pipeline()->crtc() == crtc.get(); + }); + if (!isLeased) { + crtcs.push_back(crtc.get()); + } + } + if (m_atomicModeSetting) { + // sort outputs by being already connected (to any CRTC) so that already working outputs get preferred + std::sort(connectors.begin(), connectors.end(), [](auto c1, auto c2) { + return c1->crtcId.value() > c2->crtcId.value(); + }); + } + return checkCrtcAssignment(connectors, crtcs); +} + +DrmPipeline::Error DrmGpu::testPipelines() +{ + QList inactivePipelines; + std::copy_if(m_pipelines.constBegin(), m_pipelines.constEnd(), std::back_inserter(inactivePipelines), [](const auto pipeline) { + return pipeline->enabled() && !pipeline->active(); + }); + DrmPipeline::Error test = DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::TestAllowModeset, unusedObjects()); + if (!inactivePipelines.isEmpty() && test == DrmPipeline::Error::None) { + // ensure that pipelines that are set as enabled but currently inactive + // still work when they need to be set active again + for (const auto pipeline : std::as_const(inactivePipelines)) { + pipeline->setActive(true); + } + test = DrmPipeline::commitPipelines(m_pipelines, DrmPipeline::CommitMode::TestAllowModeset, unusedObjects()); + for (const auto pipeline : std::as_const(inactivePipelines)) { + pipeline->setActive(false); + } + } + return test; +} + +DrmOutput *DrmGpu::findOutput(quint32 connector) +{ + auto it = std::find_if(m_drmOutputs.constBegin(), m_drmOutputs.constEnd(), [connector](DrmOutput *o) { + return o->connector()->id() == connector; + }); + if (it != m_drmOutputs.constEnd()) { + return *it; + } + return nullptr; +} + +void DrmGpu::waitIdle() +{ + m_socketNotifier->setEnabled(false); + while (true) { + const bool idle = std::all_of(m_drmOutputs.constBegin(), m_drmOutputs.constEnd(), [](DrmOutput *output) { + return !output->pipeline()->pageflipsPending(); + }); + if (idle) { + break; + } + pollfd pfds[1]; + pfds[0].fd = m_fd; + pfds[0].events = POLLIN; + + const int ready = poll(pfds, 1, 30000); + if (ready < 0) { + if (errno != EINTR) { + qCWarning(KWIN_DRM) << Q_FUNC_INFO << "poll() failed:" << strerror(errno); + break; + } + } else if (ready == 0) { + qCWarning(KWIN_DRM) << "No drm events for gpu" << m_devNode << "within last 30 seconds"; + break; + } else { + dispatchEvents(); + } + }; + m_socketNotifier->setEnabled(true); +} + +static std::chrono::nanoseconds convertTimestamp(const timespec ×tamp) +{ + return std::chrono::seconds(timestamp.tv_sec) + std::chrono::nanoseconds(timestamp.tv_nsec); +} + +static std::chrono::nanoseconds convertTimestamp(clockid_t sourceClock, clockid_t targetClock, + const timespec ×tamp) +{ + if (sourceClock == targetClock) { + return convertTimestamp(timestamp); + } + + timespec sourceCurrentTime = {}; + timespec targetCurrentTime = {}; + + clock_gettime(sourceClock, &sourceCurrentTime); + clock_gettime(targetClock, &targetCurrentTime); + + const auto delta = convertTimestamp(sourceCurrentTime) - convertTimestamp(timestamp); + return convertTimestamp(targetCurrentTime) - delta; +} + +void DrmGpu::pageFlipHandler(int fd, unsigned int sequence, unsigned int sec, unsigned int usec, unsigned int crtc_id, void *user_data) +{ + const auto commit = static_cast(user_data); + const auto gpu = commit->gpu(); + + // The static_cast<> here are for a 32-bit environment where + // sizeof(time_t) == sizeof(unsigned int) == 4 . Putting @p sec + // into a time_t cuts off the most-significant bit (after the + // year 2038), similarly long can't hold all the bits of an + // unsigned multiplication. + std::chrono::nanoseconds timestamp = convertTimestamp(gpu->presentationClock(), CLOCK_MONOTONIC, + {static_cast(sec), static_cast(usec * 1000)}); + if (timestamp == std::chrono::nanoseconds::zero()) { + qCDebug(KWIN_DRM, "Got invalid timestamp (sec: %u, usec: %u) on gpu %s", + sec, usec, qPrintable(gpu->devNode())); + timestamp = std::chrono::steady_clock::now().time_since_epoch(); + } + commit->pageFlipped(timestamp); +} + +void DrmGpu::dispatchEvents() +{ + drmEventContext context = {}; + context.version = 3; + context.page_flip_handler2 = pageFlipHandler; + drmHandleEvent(m_fd, &context); +} + +void DrmGpu::removeOutput(DrmOutput *output) +{ + qCDebug(KWIN_DRM) << "Removing output" << output; + m_pipelines.removeOne(output->pipeline()); + output->pipeline()->setLayers(nullptr, nullptr); + m_drmOutputs.removeOne(output); + Q_EMIT outputRemoved(output); + output->unref(); +} + +DrmBackend *DrmGpu::platform() const +{ + return m_platform; +} + +const QList DrmGpu::pipelines() const +{ + return m_pipelines; +} + +DrmVirtualOutput *DrmGpu::createVirtualOutput(const QString &name, const QSize &size, double scale) +{ + auto output = new DrmVirtualOutput(name, this, size, scale); + m_virtualOutputs << output; + Q_EMIT outputAdded(output); + return output; +} + +void DrmGpu::removeVirtualOutput(DrmVirtualOutput *output) +{ + if (m_virtualOutputs.removeOne(output)) { + Q_EMIT outputRemoved(output); + output->unref(); + } +} + +std::unique_ptr DrmGpu::leaseOutputs(const QList &outputs) +{ + QList objects; + for (DrmOutput *output : outputs) { + if (output->lease() || !output->addLeaseObjects(objects)) { + return nullptr; + } + } + + uint32_t lesseeId; + FileDescriptor fd{drmModeCreateLease(m_fd, objects.constData(), objects.count(), 0, &lesseeId)}; + if (!fd.isValid()) { + qCWarning(KWIN_DRM) << "Could not create DRM lease!" << strerror(errno); + qCWarning(KWIN_DRM) << "Tried to lease the following" << objects.count() << "resources:"; + for (const auto &res : std::as_const(objects)) { + qCWarning(KWIN_DRM) << res; + } + return nullptr; + } else { + qCDebug(KWIN_DRM) << "Created lease for" << objects.count() << "resources:"; + for (const auto &res : std::as_const(objects)) { + qCDebug(KWIN_DRM) << res; + } + return std::make_unique(this, std::move(fd), lesseeId, outputs); + } +} + +QList DrmGpu::virtualOutputs() const +{ + return m_virtualOutputs; +} + +QList DrmGpu::drmOutputs() const +{ + return m_drmOutputs; +} + +int DrmGpu::fd() const +{ + return m_fd; +} + +dev_t DrmGpu::deviceId() const +{ + return m_deviceId; +} + +bool DrmGpu::atomicModeSetting() const +{ + return m_atomicModeSetting; +} + +QString DrmGpu::devNode() const +{ + return m_devNode; +} + +gbm_device *DrmGpu::gbmDevice() const +{ + return m_gbmDevice; +} + +EglDisplay *DrmGpu::eglDisplay() const +{ + return m_eglDisplay.get(); +} + +void DrmGpu::setEglDisplay(std::unique_ptr &&display) +{ + m_eglDisplay = std::move(display); +} + +bool DrmGpu::addFB2ModifiersSupported() const +{ + return m_addFB2ModifiersSupported; +} + +bool DrmGpu::asyncPageflipSupported() const +{ + return m_asyncPageflipSupported; +} + +bool DrmGpu::isI915() const +{ + return m_isI915; +} + +bool DrmGpu::isNVidia() const +{ + return m_isNVidia; +} + +bool DrmGpu::isRemoved() const +{ + return m_isRemoved; +} + +void DrmGpu::setRemoved() +{ + m_isRemoved = true; +} + +void DrmGpu::setActive(bool active) +{ + if (m_isActive != active) { + m_isActive = active; + if (active) { + for (const auto &output : std::as_const(m_drmOutputs)) { + output->renderLoop()->uninhibit(); + } + // while the session was inactive, the output list may have changed + m_platform->updateOutputs(); + for (const auto &output : std::as_const(m_drmOutputs)) { + // force a modeset with legacy, we can't reliably know if one is needed + if (!atomicModeSetting()) { + output->pipeline()->forceLegacyModeset(); + } + } + } else { + for (const auto &output : std::as_const(m_drmOutputs)) { + output->renderLoop()->inhibit(); + } + } + Q_EMIT activeChanged(active); + } +} + +bool DrmGpu::isActive() const +{ + return m_isActive; +} + +bool DrmGpu::needsModeset() const +{ + return std::any_of(m_pipelines.constBegin(), m_pipelines.constEnd(), [](const auto &pipeline) { + return pipeline->needsModeset(); + }); +} + +bool DrmGpu::maybeModeset() +{ + auto pipelines = m_pipelines; + for (const auto &output : std::as_const(m_drmOutputs)) { + if (output->lease()) { + pipelines.removeOne(output->pipeline()); + } + } + bool presentPendingForAll = std::all_of(pipelines.constBegin(), pipelines.constEnd(), [](const auto &pipeline) { + return pipeline->modesetPresentPending() || !pipeline->activePending(); + }); + if (!presentPendingForAll) { + // commit only once all pipelines are ready for presentation + return true; + } + // make sure there's no pending pageflips + waitIdle(); + const DrmPipeline::Error err = DrmPipeline::commitPipelines(pipelines, DrmPipeline::CommitMode::CommitModeset, unusedObjects()); + for (DrmPipeline *pipeline : std::as_const(pipelines)) { + if (pipeline->modesetPresentPending()) { + pipeline->resetModesetPresentPending(); + if (err != DrmPipeline::Error::None) { + pipeline->output()->frameFailed(); + } + } + } + if (err == DrmPipeline::Error::None) { + return true; + } else { + if (err != DrmPipeline::Error::FramePending) { + QTimer::singleShot(0, m_platform, &DrmBackend::updateOutputs); + } + return false; + } +} + +QList DrmGpu::unusedObjects() const +{ + if (!m_atomicModeSetting) { + return {}; + } + QList ret = m_allObjects; + for (const auto &pipeline : m_pipelines) { + ret.removeOne(pipeline->connector()); + if (pipeline->crtc()) { + ret.removeOne(pipeline->crtc()); + ret.removeOne(pipeline->crtc()->primaryPlane()); + ret.removeOne(pipeline->crtc()->cursorPlane()); + } + } + return ret; +} + +QSize DrmGpu::cursorSize() const +{ + return m_cursorSize; +} + +void DrmGpu::releaseBuffers() +{ + for (const auto &plane : std::as_const(m_planes)) { + plane->releaseCurrentBuffer(); + } + for (const auto &crtc : std::as_const(m_crtcs)) { + crtc->releaseCurrentBuffer(); + } + for (const auto &pipeline : std::as_const(m_pipelines)) { + pipeline->primaryLayer()->releaseBuffers(); + pipeline->cursorLayer()->releaseBuffers(); + } + for (const auto &output : std::as_const(m_virtualOutputs)) { + output->primaryLayer()->releaseBuffers(); + } +} + +void DrmGpu::recreateSurfaces() +{ + for (const auto &pipeline : std::as_const(m_pipelines)) { + pipeline->setLayers(m_platform->renderBackend()->createPrimaryLayer(pipeline), m_platform->renderBackend()->createCursorLayer(pipeline)); + pipeline->applyPendingChanges(); + } + for (const auto &output : std::as_const(m_virtualOutputs)) { + output->recreateSurface(); + } +} + +GraphicsBufferAllocator *DrmGpu::graphicsBufferAllocator() const +{ + return m_allocator.get(); +} + +std::shared_ptr DrmGpu::importBuffer(GraphicsBuffer *buffer) +{ + const DmaBufAttributes *attributes = buffer->dmabufAttributes(); + if (Q_UNLIKELY(!attributes)) { + return nullptr; + } + + uint32_t handles[] = {0, 0, 0, 0}; + auto cleanup = qScopeGuard([this, &handles]() { + for (int i = 0; i < 4; ++i) { + if (handles[i] == 0) { + continue; + } + bool closed = false; + for (int j = 0; j < i; ++j) { + if (handles[i] == handles[j]) { + closed = true; + break; + } + } + if (closed) { + continue; + } + drmCloseBufferHandle(m_fd, handles[i]); + } + }); + for (int i = 0; i < attributes->planeCount; ++i) { + if (drmPrimeFDToHandle(m_fd, attributes->fd[i].get(), &handles[i]) != 0) { + qCWarning(KWIN_DRM) << "drmPrimeFDToHandle() failed"; + return nullptr; + } + } + + uint32_t framebufferId = 0; + int ret; + if (addFB2ModifiersSupported() && attributes->modifier != DRM_FORMAT_MOD_INVALID) { + uint64_t modifier[4] = {0, 0, 0, 0}; + for (int i = 0; i < attributes->planeCount; ++i) { + modifier[i] = attributes->modifier; + } + ret = drmModeAddFB2WithModifiers(m_fd, + attributes->width, + attributes->height, + attributes->format, + handles, + attributes->pitch.data(), + attributes->offset.data(), + modifier, + &framebufferId, + DRM_MODE_FB_MODIFIERS); + } else { + ret = drmModeAddFB2(m_fd, + attributes->width, + attributes->height, + attributes->format, + handles, + attributes->pitch.data(), + attributes->offset.data(), + &framebufferId, + 0); + if (ret == EOPNOTSUPP && attributes->planeCount == 1) { + ret = drmModeAddFB(m_fd, + attributes->width, + attributes->height, + 24, 32, + attributes->pitch[0], + handles[0], + &framebufferId); + } + } + + if (ret != 0) { + return nullptr; + } + + return std::make_shared(this, framebufferId, buffer); +} + +DrmLease::DrmLease(DrmGpu *gpu, FileDescriptor &&fd, uint32_t lesseeId, const QList &outputs) + : m_gpu(gpu) + , m_fd(std::move(fd)) + , m_lesseeId(lesseeId) + , m_outputs(outputs) +{ + for (const auto output : m_outputs) { + output->leased(this); + } +} + +DrmLease::~DrmLease() +{ + qCDebug(KWIN_DRM, "Revoking lease with leaseID %d", m_lesseeId); + drmModeRevokeLease(m_gpu->fd(), m_lesseeId); + for (const auto &output : m_outputs) { + output->leaseEnded(); + } +} + +FileDescriptor &DrmLease::fd() +{ + return m_fd; +} + +uint32_t DrmLease::lesseeId() const +{ + return m_lesseeId; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmgpu.h b/src/plugins/deviceintegration/drm/drmgpu.h new file mode 100644 index 00000000..064e4152 --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmgpu.h @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2020 Xaver Hugl +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "drmplane.h" + +struct gbm_device; + +namespace Aurora { + +namespace Platform { + +class DrmOutput; +class DrmObject; +class DrmCrtc; +class DrmConnector; +class DrmPlane; +class DrmBackend; +class EglGbmBackend; +class DrmAbstractOutput; +class DrmRenderBackend; +class DrmVirtualOutput; +class EglDisplay; +class GraphicsBuffer; +class GraphicsBufferAllocator; + +class DrmLease : public QObject +{ + Q_OBJECT +public: + DrmLease(DrmGpu *gpu, FileDescriptor &&fd, uint32_t lesseeId, const QList &outputs); + ~DrmLease(); + + FileDescriptor &fd(); + uint32_t lesseeId() const; + +Q_SIGNALS: + void revokeRequested(); + +private: + DrmGpu *const m_gpu; + FileDescriptor m_fd; + const uint32_t m_lesseeId; + const QList m_outputs; +}; + +class DrmGpu : public QObject +{ + Q_OBJECT +public: + DrmGpu(DrmBackend *backend, const QString &devNode, int fd, dev_t deviceId); + ~DrmGpu(); + + int fd() const; + dev_t deviceId() const; + QString devNode() const; + + bool isRemoved() const; + void setRemoved(); + void setActive(bool active); + bool isActive() const; + + bool atomicModeSetting() const; + bool addFB2ModifiersSupported() const; + bool asyncPageflipSupported() const; + bool isI915() const; + bool isNVidia() const; + gbm_device *gbmDevice() const; + EglDisplay *eglDisplay() const; + DrmBackend *platform() const; + /** + * Returns the clock from which presentation timestamps are sourced. The returned value + * can be either CLOCK_MONOTONIC or CLOCK_REALTIME. + */ + clockid_t presentationClock() const; + QSize cursorSize() const; + + QList virtualOutputs() const; + QList drmOutputs() const; + const QList pipelines() const; + + void setEglDisplay(std::unique_ptr &&display); + + bool updateOutputs(); + void removeOutputs(); + + DrmVirtualOutput *createVirtualOutput(const QString &name, const QSize &size, double scale); + void removeVirtualOutput(DrmVirtualOutput *output); + + DrmPipeline::Error testPendingConfiguration(); + bool needsModeset() const; + bool maybeModeset(); + + GraphicsBufferAllocator *graphicsBufferAllocator() const; + std::shared_ptr importBuffer(GraphicsBuffer *buffer); + void releaseBuffers(); + void recreateSurfaces(); + + FileDescriptor createNonMasterFd() const; + std::unique_ptr leaseOutputs(const QList &outputs); + void waitIdle(); + +Q_SIGNALS: + void activeChanged(bool active); + void outputAdded(DrmAbstractOutput *output); + void outputRemoved(DrmAbstractOutput *output); + +private: + void dispatchEvents(); + DrmOutput *findOutput(quint32 connector); + void removeOutput(DrmOutput *output); + void initDrmResources(); + + DrmPipeline::Error checkCrtcAssignment(QList connectors, const QList &crtcs); + DrmPipeline::Error testPipelines(); + QList unusedObjects() const; + + static void pageFlipHandler(int fd, unsigned int sequence, unsigned int sec, unsigned int usec, unsigned int crtc_id, void *user_data); + + const int m_fd; + const dev_t m_deviceId; + const QString m_devNode; + bool m_atomicModeSetting; + bool m_addFB2ModifiersSupported = false; + bool m_isNVidia; + bool m_isI915; + bool m_isVirtualMachine; + bool m_supportsCursorPlaneHotspot = false; + bool m_asyncPageflipSupported = false; + bool m_isRemoved = false; + bool m_isActive = true; + clockid_t m_presentationClock; + gbm_device *m_gbmDevice; + FileDescriptor m_gbmFd; + std::unique_ptr m_allocator; + std::unique_ptr m_eglDisplay; + DrmBackend *const m_platform; + + std::vector> m_planes; + std::vector> m_crtcs; + std::vector> m_connectors; + QList m_allObjects; + QList m_pipelines; + + QList m_drmOutputs; + QList m_virtualOutputs; + + std::unique_ptr m_socketNotifier; + QSize m_cursorSize; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmintegration.cpp b/src/plugins/deviceintegration/drm/drmintegration.cpp new file mode 100644 index 00000000..fcc6c8f4 --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmintegration.cpp @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "drmbackend.h" +#include "drmintegration.h" +#include "drmgpu.h" +#include "drmloggingcategories.h" +#include "drmoutput.h" +#include "drmwindow.h" + +#include + +namespace Aurora { + +namespace Platform { + +DrmIntegration::DrmIntegration(QObject *parent) + : DeviceIntegration(parent) +{ +} + +DrmIntegration::~DrmIntegration() +{ +} + +void DrmIntegration::initialize() +{ + DrmBackend::instance()->initialize(); +} + +void DrmIntegration::destroy() +{ + qCDebug(gLcDrm, "DRM device integration is about to be destroyed..."); + + DrmBackend::instance()->destroy(); + + qCInfo(gLcDrm, "DRM device integration destroyed successfully"); +} + +EGLNativeDisplayType DrmIntegration::platformDisplay() const +{ + return DrmBackend::instance()->platformDisplay(); +} + +EGLDisplay DrmIntegration::eglDisplay() const +{ + return DrmBackend::instance()->eglDisplay(); +} + +EGLNativeWindowType DrmIntegration::createNativeWindow(Window *window, const QSize &size, + const QSurfaceFormat &format) +{ + Q_UNUSED(format) + + auto *drmOutput = static_cast(window->output()); + auto *gbmDevice = DrmBackend::instance()->primaryGpu()->gbmDevice(); + auto *gbmSurface = + gbm_surface_create(gbmDevice, size.width(), size.height(), drmOutput->format(), + GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + + return reinterpret_cast(gbmSurface); +} + +void DrmIntegration::destroyNativeWindow(EGLNativeWindowType nativeWindow) +{ + auto *surface = reinterpret_cast(nativeWindow); + gbm_surface_destroy(surface); +} + +QSurfaceFormat DrmIntegration::surfaceFormatFor(const QSurfaceFormat &inputFormat) const +{ + QSurfaceFormat format(inputFormat); + format.setRenderableType(QSurfaceFormat::OpenGLES); + format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); + format.setRedBufferSize(8); + format.setGreenBufferSize(8); + format.setBlueBufferSize(8); + return format; +} + +void DrmIntegration::waitForVSync(Window *window) const +{ + Q_UNUSED(window) +} + +void DrmIntegration::presentBuffer(Window *window) +{ + Q_UNUSED(window) +} + +Window *DrmIntegration::createWindow(Output *output, QWindow *qtWindow) +{ + return nullptr; +} + +Window *DrmIntegration::getWindow(QWindow *qtWindow) const +{ + return nullptr; +} + +InputManager *DrmIntegration::createInputManager(QObject *parent) +{ + Q_UNUSED(parent) + return nullptr; +} + +Outputs DrmIntegration::outputs() const +{ + return Outputs(); +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmintegration.h b/src/plugins/deviceintegration/drm/drmintegration.h new file mode 100644 index 00000000..6f325337 --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmintegration.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +namespace Aurora { + +namespace Platform { + +class DrmWindow; + +class DrmIntegration : public DeviceIntegration +{ + Q_OBJECT +public: + explicit DrmIntegration(QObject *parent = nullptr); + ~DrmIntegration(); + + void initialize() override; + void destroy() override; + + EGLNativeDisplayType platformDisplay() const override; + EGLDisplay eglDisplay() const override; + + EGLNativeWindowType createNativeWindow(Window *window, const QSize &size, + const QSurfaceFormat &format) override; + void destroyNativeWindow(EGLNativeWindowType nativeWindow) override; + + QSurfaceFormat surfaceFormatFor(const QSurfaceFormat &inputFormat) const override; + + void waitForVSync(Window *window) const override; + void presentBuffer(Window *window) override; + + Window *createWindow(Output *output, QWindow *qtWindow) override; + Window *getWindow(QWindow *qtWindow) const override; + + InputManager *createInputManager(QObject *parent = nullptr) override; + + Outputs outputs() const override; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmintegrationplugin.cpp b/src/plugins/deviceintegration/drm/drmintegrationplugin.cpp new file mode 100644 index 00000000..8742cb97 --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmintegrationplugin.cpp @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "drmintegration.h" +#include "drmintegrationplugin.h" + +namespace Aurora { + +namespace Platform { + +DrmIntegrationPlugin::DrmIntegrationPlugin(QObject *parent) + : DeviceIntegrationPlugin(parent) +{ +} + +DeviceIntegration *DrmIntegrationPlugin::create() +{ + return new DrmIntegration(this); +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmintegrationplugin.h b/src/plugins/deviceintegration/drm/drmintegrationplugin.h new file mode 100644 index 00000000..ec9d24bf --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmintegrationplugin.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +namespace Aurora { + +namespace Platform { + +class DrmIntegrationPlugin : public DeviceIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "io.liri.Aurora.DeviceIntegrationPlugin/1" FILE "drm.json") +public: + explicit DrmIntegrationPlugin(QObject *parent = nullptr); + + DeviceIntegration *create() override; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmobject.cpp b/src/plugins/deviceintegration/drm/drmobject.cpp new file mode 100644 index 00000000..c6839bd0 --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmobject.cpp @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2016 Roman Gilg +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "drmgpu.h" +#include "drmloggingcategories.h" +#include "drmobject.h" +#include "drmpointer.h" + +#include + +namespace Aurora { + +namespace Platform { + +DrmObject::DrmObject(DrmGpu *gpu, uint32_t objectId, uint32_t objectType) + : m_gpu(gpu) + , m_id(objectId) + , m_objectType(objectType) +{ +} + +bool DrmObject::init() +{ + return updateProperties(); +} + +DrmPropertyList DrmObject::queryProperties() const +{ + DrmUniquePtr properties( + drmModeObjectGetProperties(m_gpu->fd(), m_id, m_objectType)); + if (!properties) { + qCWarning(gLcDrm) << "Failed to get properties for object" << m_id; + return {}; + } + DrmPropertyList ret; + for (uint32_t i = 0; i < properties->count_props; i++) { + DrmUniquePtr prop( + drmModeGetProperty(m_gpu->fd(), properties->props[i])); + if (!prop) { + qCWarning(gLcDrm, "Getting property %d of object %d failed!", properties->props[i], + m_id); + continue; + } + ret.addProperty(std::move(prop), properties->prop_values[i]); + } + return ret; +} + +uint32_t DrmObject::id() const +{ + return m_id; +} + +uint32_t DrmObject::type() const +{ + return m_objectType; +} + +QString DrmObject::typeName() const +{ + switch (m_objectType) { + case DRM_MODE_OBJECT_CONNECTOR: + return QStringLiteral("connector"); + case DRM_MODE_OBJECT_CRTC: + return QStringLiteral("crtc"); + case DRM_MODE_OBJECT_PLANE: + return QStringLiteral("plane"); + default: + return QStringLiteral("unknown?"); + } +} + +DrmGpu *DrmObject::gpu() const +{ + return m_gpu; +} + +void DrmPropertyList::addProperty(DrmUniquePtr &&prop, uint64_t value) +{ + m_properties.push_back(std::make_pair(std::move(prop), value)); +} + +std::optional, uint64_t>> +DrmPropertyList::takeProperty(const QByteArray &name) +{ + const auto it = std::find_if(m_properties.begin(), m_properties.end(), + [&name](const auto &pair) { return pair.first->name == name; }); + if (it != m_properties.end()) { + auto ret = std::move(*it); + m_properties.erase(it); + return ret; + } else { + return std::nullopt; + } +} + +} // namespace Platform + +} // namespace Aurora + +QDebug operator<<(QDebug s, const Aurora::Platform::DrmObject *obj) +{ + QDebugStateSaver saver(s); + if (obj) + s.nospace() << "DrmObject(id=" << obj->id() << ", gpu=" << obj->gpu() << ')'; + else + s << "DrmObject(0x0)"; + return s; +} diff --git a/src/plugins/deviceintegration/drm/drmobject.h b/src/plugins/deviceintegration/drm/drmobject.h new file mode 100644 index 00000000..55a541fa --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmobject.h @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2016 Roman Gilg +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include + +#include + +#include "drmpointer.h" +#include "drmproperty.h" + +namespace Aurora { + +namespace Platform { + +class DrmAtomicCommit; +class DrmGpu; + +class DrmPropertyList +{ +public: + void addProperty(DrmUniquePtr &&prop, uint64_t value); + std::optional, uint64_t>> + takeProperty(const QByteArray &name); + +private: + std::vector, uint64_t>> m_properties; +}; + +class DrmObject +{ +public: + virtual ~DrmObject() = default; + DrmObject(const DrmObject &) = delete; + + /** + * Must be called to query necessary data directly after creation. + * @return true when initializing was successful + */ + bool init(); + + /** + * Set the properties in such a way that this resource won't be used anymore + */ + virtual void disable(DrmAtomicCommit *commit) = 0; + + virtual bool updateProperties() = 0; + + uint32_t id() const; + uint32_t type() const; + QString typeName() const; + DrmGpu *gpu() const; + +protected: + DrmObject(DrmGpu *gpu, uint32_t objectId, uint32_t objectType); + + DrmPropertyList queryProperties() const; + +private: + DrmGpu *m_gpu; + const uint32_t m_id; + const uint32_t m_objectType; +}; + +} // namespace Platform + +} // namespace Aurora + +QDebug operator<<(QDebug stream, const Aurora::Platform::DrmObject *); diff --git a/src/plugins/deviceintegration/drm/drmoutput.cpp b/src/plugins/deviceintegration/drm/drmoutput.cpp new file mode 100644 index 00000000..ca8ef48f --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmoutput.cpp @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "drmoutput.h" + +namespace Aurora { + +namespace Platform { + +DrmOutput::DrmOutput(QObject *parent) + : Output(parent) +{ +} + +uint32_t DrmOutput::format() const +{ + return 0; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmoutput.h b/src/plugins/deviceintegration/drm/drmoutput.h new file mode 100644 index 00000000..07f5d18f --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmoutput.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +namespace Aurora { + +namespace Platform { + +class DrmOutput : public Output +{ + Q_OBJECT +public: + explicit DrmOutput(QObject *parent = nullptr); + + uint32_t format() const; +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmplane.cpp b/src/plugins/deviceintegration/drm/drmplane.cpp new file mode 100644 index 00000000..073ccaaf --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmplane.cpp @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "drmplane.h" + +namespace Aurora { + +namespace Platform { + +DrmPlane::DrmPlane(uint32_t id, DrmGpu *gpu) + : DrmObject(id, DRM_MODE_OBJECT_PLANE, gpu) +{ +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmplane.h b/src/plugins/deviceintegration/drm/drmplane.h new file mode 100644 index 00000000..17184180 --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmplane.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "drmobject.h" + +namespace Aurora { + +namespace Platform { + +class DrmPlane : public DrmObject +{ +public: + explicit DrmPlane(uint32_t id, DrmGpu *gpu); +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmpointer.h b/src/plugins/deviceintegration/drm/drmpointer.h new file mode 100644 index 00000000..bfbf8d6b --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmpointer.h @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2015 Martin Gräßlin +// SPDX-FileCopyrightText: 2019 Vlad Zahorodnii +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +namespace Aurora { + +namespace Platform { + +template +struct DrmDeleter; + +template <> +struct DrmDeleter +{ + void operator()(drmVersion *version) + { + drmFreeVersion(version); + } +}; + +template <> +struct DrmDeleter +{ + void operator()(drmModeAtomicReq *req) + { + drmModeAtomicFree(req); + } +}; + +template <> +struct DrmDeleter +{ + void operator()(drmModeConnector *connector) + { + drmModeFreeConnector(connector); + } +}; + +template <> +struct DrmDeleter +{ + void operator()(drmModeCrtc *crtc) + { + drmModeFreeCrtc(crtc); + } +}; + +template <> +struct DrmDeleter +{ + void operator()(drmModeFB *fb) + { + drmModeFreeFB(fb); + } +}; + +template <> +struct DrmDeleter +{ + void operator()(drmModeEncoder *encoder) + { + drmModeFreeEncoder(encoder); + } +}; + +template <> +struct DrmDeleter +{ + void operator()(drmModeModeInfo *info) + { + drmModeFreeModeInfo(info); + } +}; + +template <> +struct DrmDeleter +{ + void operator()(drmModeObjectProperties *properties) + { + drmModeFreeObjectProperties(properties); + } +}; + +template <> +struct DrmDeleter +{ + void operator()(drmModePlane *plane) + { + drmModeFreePlane(plane); + } +}; + +template <> +struct DrmDeleter +{ + void operator()(drmModePlaneRes *resources) + { + drmModeFreePlaneResources(resources); + } +}; + +template <> +struct DrmDeleter +{ + void operator()(drmModePropertyRes *property) + { + drmModeFreeProperty(property); + } +}; + +template <> +struct DrmDeleter +{ + void operator()(drmModePropertyBlobRes *blob) + { + drmModeFreePropertyBlob(blob); + } +}; + +template <> +struct DrmDeleter +{ + void operator()(drmModeRes *resources) + { + drmModeFreeResources(resources); + } +}; + +template <> +struct DrmDeleter +{ + void operator()(drmModeLesseeListRes *ptr) + { + drmFree(ptr); + } +}; + +template +using DrmUniquePtr = std::unique_ptr>; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmproperty.cpp b/src/plugins/deviceintegration/drm/drmproperty.cpp new file mode 100644 index 00000000..1897f2bb --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmproperty.cpp @@ -0,0 +1,145 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2016 Roman Gilg +// SPDX-FileCopyrightText: 2021-2022 Xaver Hugl +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "drmgpu.h" +#include "drmobject.h" +#include "drmproperty.h" + +#include + +namespace Aurora { + +namespace Platform { + +DrmProperty::DrmProperty(DrmObject *obj, const QByteArray &name, + const QVector &enumNames) + : m_obj(obj) + , m_propName(name) + , m_enumNames(enumNames) +{ +} + +bool DrmProperty::setPropertyLegacy(uint64_t value) +{ + if (m_current == value) { + return true; + } else if (drmModeObjectSetProperty(m_obj->gpu()->fd(), m_obj->id(), m_obj->type(), m_propId, + value) + == 0) { + m_current = value; + return true; + } else { + return false; + } +} + +void DrmProperty::update(DrmPropertyList &propertyList) +{ + if (const auto opt = propertyList.takeProperty(m_propName)) { + const auto &[prop, value] = *opt; + m_propId = prop->prop_id; + m_current = value; + m_immutable = prop->flags & DRM_MODE_PROP_IMMUTABLE; + m_isBlob = prop->flags & DRM_MODE_PROP_BLOB; + m_isBitmask = prop->flags & DRM_MODE_PROP_BITMASK; + if (prop->flags & DRM_MODE_PROP_RANGE) { + Q_ASSERT(prop->count_values > 1); + m_minValue = prop->values[0]; + m_maxValue = prop->values[1]; + } + m_enumToPropertyMap.clear(); + m_propertyToEnumMap.clear(); + // bitmasks need translation too, not just enums + if (prop->flags & (DRM_MODE_PROP_ENUM | DRM_MODE_PROP_BITMASK)) { + for (int i = 0; i < prop->count_enums; i++) { + struct drm_mode_property_enum *en = &prop->enums[i]; + int j = m_enumNames.indexOf(QByteArray(en->name)); + if (j >= 0) { + if (m_isBitmask) { + m_enumToPropertyMap[1 << j] = 1 << en->value; + m_propertyToEnumMap[1 << en->value] = 1 << j; + } else { + m_enumToPropertyMap[j] = en->value; + m_propertyToEnumMap[en->value] = j; + } + } + } + } + if (m_immutable && m_isBlob) { + if (m_current != 0) { + m_immutableBlob.reset(drmModeGetPropertyBlob(m_obj->gpu()->fd(), m_current)); + if (m_immutableBlob && (!m_immutableBlob->data || !m_immutableBlob->length)) { + m_immutableBlob.reset(); + } + } else { + m_immutableBlob.reset(); + } + } + } else { + m_propId = 0; + m_immutableBlob.reset(); + m_enumToPropertyMap.clear(); + m_propertyToEnumMap.clear(); + } +} + +uint64_t DrmProperty::value() const +{ + return m_current; +} + +bool DrmProperty::hasAllEnums() const +{ + return m_enumToPropertyMap.count() == m_enumNames.count(); +} + +uint32_t DrmProperty::propId() const +{ + return m_propId; +} + +const QByteArray &DrmProperty::name() const +{ + return m_propName; +} + +bool DrmProperty::isImmutable() const +{ + return m_immutable; +} + +bool DrmProperty::isBitmask() const +{ + return m_isBitmask; +} + +uint64_t DrmProperty::minValue() const +{ + return m_minValue; +} + +uint64_t DrmProperty::maxValue() const +{ + return m_maxValue; +} + +drmModePropertyBlobRes *DrmProperty::immutableBlob() const +{ + return m_immutableBlob.get(); +} + +DrmObject *DrmProperty::drmObject() const +{ + return m_obj; +} + +bool DrmProperty::isValid() const +{ + return m_propId != 0; +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmproperty.h b/src/plugins/deviceintegration/drm/drmproperty.h new file mode 100644 index 00000000..c38522c7 --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmproperty.h @@ -0,0 +1,132 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-FileCopyrightText: 2016 Roman Gilg +// SPDX-FileCopyrightText: 2021-2022 Xaver Hugl +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include + +#include "drmpointer.h" + +namespace Aurora { + +namespace Platform { + +class DrmObject; +class DrmPropertyList; + +class DrmProperty +{ +public: + DrmProperty(DrmObject *obj, const QByteArray &name, const QVector &enumNames = {}); + + const QByteArray &name() const; + DrmObject *drmObject() const; + + uint32_t propId() const; + bool isImmutable() const; + bool isBitmask() const; + bool hasAllEnums() const; + uint64_t value() const; + drmModePropertyBlobRes *immutableBlob() const; + uint64_t minValue() const; + uint64_t maxValue() const; + bool isValid() const; + + void update(DrmPropertyList &propertyList); + bool setPropertyLegacy(uint64_t value); + +protected: + DrmObject *const m_obj; + const QByteArray m_propName; + const QVector m_enumNames; + + uint32_t m_propId = 0; + // the last known value from the kernel + uint64_t m_current = 0; + DrmUniquePtr m_immutableBlob; + + uint64_t m_minValue = -1; + uint64_t m_maxValue = -1; + + QMap m_enumToPropertyMap; + QMap m_propertyToEnumMap; + bool m_immutable = false; + bool m_isBlob = false; + bool m_isBitmask = false; +}; + +template +class DrmEnumProperty : public DrmProperty +{ +public: + DrmEnumProperty(DrmObject *obj, const QByteArray &name, const QVector &enumNames) + : DrmProperty(obj, name, enumNames) + { + } + + Enum enumValue() const + { + return enumForValue(value()); + } + + bool hasEnum(Enum value) const + { + const uint64_t integerValue = static_cast(value); + if (m_isBitmask) { + for (uint64_t mask = 1; integerValue >= mask && mask != 0; mask <<= 1) { + if ((integerValue & mask) && !m_enumToPropertyMap.contains(mask)) + return false; + } + return true; + } else { + return m_enumToPropertyMap.contains(integerValue); + } + } + + Enum enumForValue(uint64_t value) const + { + if (m_isBitmask) { + uint64_t ret = 0; + for (uint64_t mask = 1; value >= mask && mask != 0; mask <<= 1) { + if (value & mask) + ret |= m_propertyToEnumMap[mask]; + } + return static_cast(ret); + } else { + return static_cast(m_propertyToEnumMap[value]); + } + } + + uint64_t valueForEnum(Enum enumValue) const + { + const uint64_t integer = static_cast(enumValue); + if (m_isBitmask) { + uint64_t set = 0; + for (uint64_t mask = 1; integer >= mask && mask != 0; mask <<= 1) { + if (integer & mask) + set |= m_enumToPropertyMap[mask]; + } + return set; + } else { + return m_enumToPropertyMap[integer]; + } + } + + bool setEnumLegacy(Enum value) + { + if (hasEnum(value)) + return setPropertyLegacy(valueForEnum(value)); + else + return false; + } +}; + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmwindow.cpp b/src/plugins/deviceintegration/drm/drmwindow.cpp new file mode 100644 index 00000000..0a1c13e3 --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmwindow.cpp @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "drmcursor.h" +#include "drmwindow.h" + +namespace Aurora { + +namespace Platform { + +DrmWindow::DrmWindow(DrmOutput *output, QWindow *qtWindow, QObject *parent) + : Window(output, qtWindow, parent) + , m_cursor(new DrmCursor(this)) +{ +} + +} // namespace Platform + +} // namespace Aurora diff --git a/src/plugins/deviceintegration/drm/drmwindow.h b/src/plugins/deviceintegration/drm/drmwindow.h new file mode 100644 index 00000000..0e845d46 --- /dev/null +++ b/src/plugins/deviceintegration/drm/drmwindow.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 Pier Luigi Fiorini +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "drmoutput.h" + +namespace Aurora { + +namespace Platform { + +class DrmCursor; + +class DrmWindow : public Window +{ + Q_OBJECT +public: + explicit DrmWindow(DrmOutput *output, QWindow *qtWindow, QObject *parent = nullptr); + +private: + DrmCursor *m_cursor = nullptr; +}; + +} // namespace Platform + +} // namespace Aurora