diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index b5d01ddd5f..d63c61fbf9 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(libkwineffects) add_subdirectory(integration) add_subdirectory(libinput) add_subdirectory(tabbox) +add_subdirectory(drm) ######################################################## # Test WindowPaintData diff --git a/autotests/drm/CMakeLists.txt b/autotests/drm/CMakeLists.txt new file mode 100644 index 0000000000..8a3aefedf2 --- /dev/null +++ b/autotests/drm/CMakeLists.txt @@ -0,0 +1,62 @@ +set(mockDRM_SRCS + mock_drm.cpp + ../../src/backends/drm/drm_abstract_output.cpp + ../../src/backends/drm/drm_backend.cpp + ../../src/backends/drm/drm_buffer.cpp + ../../src/backends/drm/drm_buffer_gbm.cpp + ../../src/backends/drm/drm_dmabuf_feedback.cpp + ../../src/backends/drm/drm_dumb_buffer.cpp + ../../src/backends/drm/drm_dumb_swapchain.cpp + ../../src/backends/drm/drm_egl_backend.cpp + ../../src/backends/drm/drm_egl_cursor_layer.cpp + ../../src/backends/drm/drm_egl_layer.cpp + ../../src/backends/drm/drm_egl_layer_surface.cpp + ../../src/backends/drm/drm_gbm_surface.cpp + ../../src/backends/drm/drm_gpu.cpp + ../../src/backends/drm/drm_layer.cpp + ../../src/backends/drm/drm_logging.cpp + ../../src/backends/drm/drm_connector.cpp + ../../src/backends/drm/drm_object.cpp + ../../src/backends/drm/drm_crtc.cpp + ../../src/backends/drm/drm_plane.cpp + ../../src/backends/drm/drm_output.cpp + ../../src/backends/drm/drm_pipeline.cpp + ../../src/backends/drm/drm_pipeline_legacy.cpp + ../../src/backends/drm/drm_property.cpp + ../../src/backends/drm/drm_qpainter_backend.cpp + ../../src/backends/drm/drm_qpainter_layer.cpp + ../../src/backends/drm/drm_shadow_buffer.cpp + ../../src/backends/drm/drm_virtual_egl_layer.cpp + ../../src/backends/drm/drm_virtual_output.cpp +) + +include_directories(${Libdrm_INCLUDE_DIRS}) + +add_library(LibDrmTest STATIC ${mockDRM_SRCS}) +target_link_libraries(LibDrmTest + Qt::Gui + Qt::Widgets + KF5::ConfigCore + KF5::WindowSystem + KF5::CoreAddons + KF5::I18n + XCB::XCB + PkgConfig::Libxcvt + gbm::gbm + kwin +) +target_include_directories(LibDrmTest + PUBLIC + ../../src + ../../src/platformsupport/scenes/opengl + ../../src/platformsupport/scenes/qpainter + ../../src/backends/drm/ +) + +######################################################## +# Tests +######################################################## +add_executable(testDrm drmTest.cpp) +target_link_libraries(testDrm LibDrmTest Qt::Test) +add_test(NAME kwin-testDrm COMMAND testDrm) +ecm_mark_as_test(testDrm) diff --git a/autotests/drm/drmTest.cpp b/autotests/drm/drmTest.cpp new file mode 100644 index 0000000000..ec23185865 --- /dev/null +++ b/autotests/drm/drmTest.cpp @@ -0,0 +1,162 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2022 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +#include "mock_drm.h" + +#include "drm_backend.h" +#include "drm_dumb_buffer.h" +#include "drm_egl_backend.h" +#include "drm_gpu.h" +#include "drm_connector.h" +#include "drm_crtc.h" +#include "drm_plane.h" +#include "drm_output.h" +#include "drm_pipeline.h" +#include "drm_pointer.h" +#include "qpainterbackend.h" +#include "core/session.h" + +#include + +using namespace KWin; + +class DrmTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testAmsDetection(); + void testOutputDetection(); + void testZeroModesHandling(); +}; + +static void verifyCleanup(MockGpu *mockGpu) +{ + QVERIFY(mockGpu->drmConnectors.isEmpty()); + QVERIFY(mockGpu->drmEncoders.isEmpty()); + QVERIFY(mockGpu->drmCrtcs.isEmpty()); + QVERIFY(mockGpu->drmPlanes.isEmpty()); + QVERIFY(mockGpu->drmPlaneRes.isEmpty()); + QVERIFY(mockGpu->fbs.isEmpty()); + QVERIFY(mockGpu->drmProps.isEmpty()); + QVERIFY(mockGpu->drmObjectProperties.isEmpty()); + QVERIFY(mockGpu->drmPropertyBlobs.isEmpty()); +} + +void DrmTest::testAmsDetection() +{ + const auto mockGpu = std::make_unique(1, 0); + + const auto session = Session::create(Session::Type::Noop); + const auto backend = std::make_unique(session.get()); + + // gpu without planes should use legacy mode + auto gpu = std::make_unique(backend.get(), "legacy", 1, 0); + QVERIFY(!gpu->atomicModeSetting()); + + // gpu with planes should use AMS + mockGpu->planes << std::make_shared(mockGpu.get(), PlaneType::Primary, 0); + gpu = std::make_unique(backend.get(), "AMS", 1, 0); + QVERIFY(gpu->atomicModeSetting()); + + // but not if the kernel doesn't allow it + mockGpu->deviceCaps[MOCKDRM_DEVICE_CAP_ATOMIC] = 0; + gpu = std::make_unique(backend.get(), "legacy 2", 1, 0); + QVERIFY(!gpu->atomicModeSetting()); + + gpu.reset(); + verifyCleanup(mockGpu.get()); +} + +void DrmTest::testOutputDetection() +{ + const auto mockGpu = std::make_unique(1, 5); + + const auto one = std::make_shared(mockGpu.get()); + const auto two = std::make_shared(mockGpu.get()); + const auto vr = std::make_shared(mockGpu.get(), true); + mockGpu->connectors.push_back(one); + mockGpu->connectors.push_back(two); + mockGpu->connectors.push_back(vr); + + const auto session = Session::create(Session::Type::Noop); + const auto backend = std::make_unique(session.get()); + const auto renderBackend = backend->createQPainterBackend(); + auto gpu = std::make_unique(backend.get(), "test", 1, 0); + QVERIFY(gpu->updateOutputs()); + + // 3 outputs should be detected, one of them non-desktop + const auto outputs = gpu->drmOutputs(); + QCOMPARE(outputs.size(), 3); + const auto vrOutput = std::find_if(outputs.begin(), outputs.end(), [](const auto &output) { + return output->isNonDesktop(); + }); + QVERIFY(vrOutput != outputs.end()); + QVERIFY(static_cast(*vrOutput)->connector()->id() == vr->id); + + // test hotunplugging + mockGpu->connectors.removeOne(one); + QVERIFY(gpu->updateOutputs()); + QCOMPARE(gpu->drmOutputs().size(), 2); + + // test hotplugging + mockGpu->connectors.push_back(one); + QVERIFY(gpu->updateOutputs()); + QCOMPARE(gpu->drmOutputs().size(), 3); + + // connector state changing to disconnected should count as a hotunplug + one->connection = DRM_MODE_DISCONNECTED; + QVERIFY(gpu->updateOutputs()); + QCOMPARE(gpu->drmOutputs().size(), 2); + + // don't crash if all connectors are disconnected + two->connection = DRM_MODE_DISCONNECTED; + vr->connection = DRM_MODE_DISCONNECTED; + QVERIFY(gpu->updateOutputs()); + QVERIFY(gpu->drmOutputs().empty()); + + gpu.reset(); + verifyCleanup(mockGpu.get()); +} + +void DrmTest::testZeroModesHandling() +{ + const auto mockGpu = std::make_unique(1, 5); + + const auto conn = std::make_shared(mockGpu.get()); + mockGpu->connectors.push_back(conn); + + const auto session = Session::create(Session::Type::Noop); + const auto backend = std::make_unique(session.get()); + const auto renderBackend = backend->createQPainterBackend(); + auto gpu = std::make_unique(backend.get(), "test", 1, 0); + + // connector with zero modes should be ignored + conn->modes.clear(); + QVERIFY(gpu->updateOutputs()); + QVERIFY(gpu->drmOutputs().empty()); + + // once it has modes, it should be detected + conn->addMode(1920, 1080, 60); + QVERIFY(gpu->updateOutputs()); + QCOMPARE(gpu->drmOutputs().size(), 1); + + // if an update says it has no modes anymore but it's still connected, ignore that + conn->modes.clear(); + QVERIFY(gpu->updateOutputs()); + QCOMPARE(gpu->drmOutputs().size(), 1); + QVERIFY(!gpu->drmOutputs().constFirst()->modes().empty()); + + gpu.reset(); + verifyCleanup(mockGpu.get()); +} + +QTEST_GUILESS_MAIN(DrmTest) +#include "drmTest.moc" diff --git a/autotests/drm/mock_drm.cpp b/autotests/drm/mock_drm.cpp new file mode 100644 index 0000000000..a86e367417 --- /dev/null +++ b/autotests/drm/mock_drm.cpp @@ -0,0 +1,1338 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2022 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "mock_drm.h" + +#include +extern "C" { +#include +} +#include + +#include +#include + +// Mock impls + +static QMap s_gpus; + +static MockGpu *getGpu(int fd) +{ + return s_gpus[fd]; +} + +MockGpu::MockGpu(int fd, int numCrtcs, int gammaSize) + : fd(fd) +{ + s_gpus.insert(fd, this); + for (int i = 0; i < numCrtcs; i++) { + const auto &plane = std::make_shared(this, PlaneType::Primary, i); + crtcs << std::make_shared(this, plane, i, gammaSize); + planes << plane; + } + deviceCaps.insert(MOCKDRM_DEVICE_CAP_ATOMIC, 1); + deviceCaps.insert(DRM_CAP_DUMB_BUFFER, 1); + deviceCaps.insert(DRM_CAP_PRIME, DRM_PRIME_CAP_IMPORT | DRM_PRIME_CAP_EXPORT); + deviceCaps.insert(DRM_CAP_ASYNC_PAGE_FLIP, 0); + deviceCaps.insert(DRM_CAP_ADDFB2_MODIFIERS, 1); +} + +MockGpu::~MockGpu() +{ + s_gpus.remove(fd); +} + +MockPropertyBlob *MockGpu::getBlob(uint32_t id) const +{ + auto it = std::find_if(propertyBlobs.begin(), propertyBlobs.end(), [id](const auto &propBlob) { + return propBlob->id == id; + }); + return it == propertyBlobs.end() ? nullptr : it->get(); +} + +MockConnector *MockGpu::findConnector(uint32_t id) const +{ + auto it = std::find_if(connectors.constBegin(), connectors.constEnd(), [id](const auto &c){return c->id == id;}); + return it == connectors.constEnd() ? nullptr : (*it).get(); +} + +MockCrtc *MockGpu::findCrtc(uint32_t id) const +{ + auto it = std::find_if(crtcs.constBegin(), crtcs.constEnd(), [id](const auto &c){return c->id == id;}); + return it == crtcs.constEnd() ? nullptr : (*it).get(); +} + +MockPlane *MockGpu::findPlane(uint32_t id) const +{ + auto it = std::find_if(planes.constBegin(), planes.constEnd(), [id](const auto &p){return p->id == id;}); + return it == planes.constEnd() ? nullptr : (*it).get(); +} + +void MockGpu::flipPage(uint32_t crtcId) +{ + auto crtc = findCrtc(crtcId); + Q_ASSERT(crtc); + for (const auto &plane : qAsConst(planes)) { + if (plane->getProp(QStringLiteral("CRTC_ID")) == crtc->id) { + plane->currentFb = plane->nextFb; + } + } + // TODO page flip event? +} + +// + +MockObject::MockObject(MockGpu *gpu) + : id(gpu->idCounter++) + , gpu(gpu) +{ + gpu->objects << this; +} + +MockObject::~MockObject() +{ + gpu->objects.removeOne(this); +} + +uint64_t MockObject::getProp(const QString &propName) const +{ + for (const auto &prop : qAsConst(props)) { + if (prop.name == propName) { + return prop.value; + } + } + Q_UNREACHABLE(); +} + +void MockObject::setProp(const QString &propName, uint64_t value) +{ + for (auto &prop : props) { + if (prop.name == propName) { + prop.value = value; + return; + } + } + Q_UNREACHABLE(); +} + +uint32_t MockObject::getPropId(const QString &propName) const +{ + for (const auto &prop : qAsConst(props)) { + if (prop.name == propName) { + return prop.id; + } + } + Q_UNREACHABLE(); +} + +// + +MockProperty::MockProperty(MockObject *obj, QString name, uint64_t initialValue, uint32_t flags, QVector enums) + : obj(obj) + , id(obj->gpu->idCounter++) + , flags(flags) + , name(name) + , value(initialValue) + , enums(enums) +{ + qDebug("Added property %s with id %u to object %u", qPrintable(name), id, obj->id); +} + +MockPropertyBlob::MockPropertyBlob(MockGpu *gpu, const void *d, size_t size) + : gpu(gpu) + , id(gpu->idCounter++) + , data(malloc(size)) + , size(size) +{ + memcpy(data, d, size); +} + +MockPropertyBlob::~MockPropertyBlob() +{ + free(data); +} + +// + +#define addProp(name, value, flags) props << MockProperty(this, QStringLiteral(name), value, flags) + +MockConnector::MockConnector(MockGpu *gpu, bool nonDesktop) + : MockObject(gpu) + , connection(DRM_MODE_CONNECTED) + , type(DRM_MODE_CONNECTOR_DisplayPort) + , encoder(std::make_shared(gpu, 0xFF)) +{ + gpu->encoders << encoder; + addProp("CRTC_ID", 0, DRM_MODE_PROP_ATOMIC); + + addProp("Subpixel", DRM_MODE_SUBPIXEL_UNKNOWN, DRM_MODE_PROP_IMMUTABLE); + addProp("non-desktop", nonDesktop, DRM_MODE_PROP_IMMUTABLE); + addProp("vrr_capable", 0, DRM_MODE_PROP_IMMUTABLE); + + addProp("DPMS", DRM_MODE_DPMS_OFF, 0); + addProp("EDID", 0, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE); + + addMode(1920, 1080, 60.0); +} + +void MockConnector::addMode(uint32_t width, uint32_t height, float refreshRate, bool preferred) +{ + auto modeInfo = libxcvt_gen_mode_info(width, height, refreshRate, false, false); + + drmModeModeInfo mode{ + .clock = uint32_t(modeInfo->dot_clock), + .hdisplay = uint16_t(modeInfo->hdisplay), + .hsync_start = modeInfo->hsync_start, + .hsync_end = modeInfo->hsync_end, + .htotal = modeInfo->htotal, + .hskew = 0, + .vdisplay = uint16_t(modeInfo->vdisplay), + .vsync_start = modeInfo->vsync_start, + .vsync_end = modeInfo->vsync_end, + .vtotal = modeInfo->vtotal, + .vscan = 1, + .vrefresh = uint32_t(modeInfo->vrefresh), + .flags = modeInfo->mode_flags, + .type = DRM_MODE_TYPE_DRIVER, + }; + if (preferred) { + mode.type |= DRM_MODE_TYPE_PREFERRED; + } + sprintf(mode.name, "%dx%d@%d", width, height, mode.vrefresh); + + modes.push_back(mode); + free(modeInfo); +} + +// + +MockCrtc::MockCrtc(MockGpu *gpu, const std::shared_ptr &legacyPlane, int pipeIndex, int gamma_size) + : MockObject(gpu) + , pipeIndex(pipeIndex) + , gamma_size(gamma_size) + , legacyPlane(legacyPlane) +{ + addProp("MODE_ID", 0, DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_BLOB); + addProp("ACTIVE", 0, DRM_MODE_PROP_ATOMIC); + addProp("GAMMA_LUT", 0, DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_BLOB); + addProp("GAMMA_LUT_SIZE", gamma_size, DRM_MODE_PROP_ATOMIC); +} + + +// + +MockPlane::MockPlane(MockGpu *gpu, PlaneType type, int crtcIndex) + : MockObject(gpu) + , possibleCrtcs(1 << crtcIndex) + , type(type) +{ + props << MockProperty(this, QStringLiteral("type"), static_cast(type), DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_ENUM | DRM_MODE_PROP_BITMASK, + {QByteArrayLiteral("Primary"), QByteArrayLiteral("Overlay"), QByteArrayLiteral("Cursor")}); + addProp("FB_ID", 0, DRM_MODE_PROP_ATOMIC); + addProp("CRTC_ID", 0, DRM_MODE_PROP_ATOMIC); + addProp("CRTC_X", 0, DRM_MODE_PROP_ATOMIC); + addProp("CRTC_Y", 0, DRM_MODE_PROP_ATOMIC); + addProp("CRTC_W", 0, DRM_MODE_PROP_ATOMIC); + addProp("CRTC_H", 0, DRM_MODE_PROP_ATOMIC); + addProp("SRC_X", 0, DRM_MODE_PROP_ATOMIC); + addProp("SRC_Y", 0, DRM_MODE_PROP_ATOMIC); + addProp("SRC_W", 0, DRM_MODE_PROP_ATOMIC); + addProp("SRC_H", 0, DRM_MODE_PROP_ATOMIC); +} + +// + +MockEncoder::MockEncoder(MockGpu* gpu, uint32_t possible_crtcs) + : MockObject(gpu) + , possible_crtcs(possible_crtcs) +{ +} + + +// + +MockFb::MockFb(MockGpu *gpu, uint32_t width, uint32_t height) + : id(gpu->idCounter++) + , width(width) + , height(height) + , gpu(gpu) +{ + gpu->fbs << this; +} + +MockFb::~MockFb() +{ + gpu->fbs.removeOne(this); +} + +// + +MockDumbBuffer::MockDumbBuffer(MockGpu *gpu, uint32_t width, uint32_t height, uint32_t bpp) + : handle(gpu->idCounter++) + , pitch(width * ceil(bpp / 8.0)) + , size(height * pitch) + , data(malloc(size)) + , gpu(gpu) +{ +} + +MockDumbBuffer::~MockDumbBuffer() +{ + free(data); +} + +// drm functions + +#define GPU(fd, error) auto gpu = getGpu(fd);\ +if (!gpu) {\ + qWarning("invalid fd %d", fd);\ + errno = EINVAL;\ + return error;\ +} + +drmVersionPtr drmGetVersion(int fd) +{ + GPU(fd, nullptr); + drmVersionPtr ptr = new drmVersion; + ptr->name = new char[gpu->name.size() + 1]; + strcpy(ptr->name, gpu->name.data()); + return ptr; +} + +void drmFreeVersion(drmVersionPtr ptr) +{ + Q_ASSERT(ptr); + delete[] ptr->name; + delete ptr; +} + +int drmSetClientCap(int fd, uint64_t capability, uint64_t value) +{ + GPU(fd, -EINVAL); + if (capability == DRM_CLIENT_CAP_ATOMIC) { + if (!gpu->deviceCaps[MOCKDRM_DEVICE_CAP_ATOMIC]) { + return -(errno = ENOTSUP); + } + qDebug("Setting DRM_CLIENT_CAP_ATOMIC to %lu", value); + } + gpu->clientCaps.insert(capability, value); + return 0; +} + +int drmGetCap(int fd, uint64_t capability, uint64_t *value) +{ + GPU(fd, -EINVAL); + if (gpu->deviceCaps.contains(capability)) { + *value = gpu->deviceCaps[capability]; + return 0; + } + qDebug("Could not find capability %lu", capability); + return -(errno = EINVAL); +} + +int drmHandleEvent(int fd, drmEventContextPtr evctx) +{ + GPU(fd, -EINVAL); + // TODO ? + Q_UNUSED(evctx) + return -(errno = ENOTSUP); +} + +int drmIoctl(int fd, unsigned long request, void *arg) +{ + GPU(fd, -EINVAL); + Q_UNUSED(arg); + if (request == DRM_IOCTL_MODE_CREATE_DUMB) { + auto args = static_cast(arg); + auto dumb = std::make_shared(gpu, args->width, args->height, args->bpp); + args->handle = dumb->handle; + args->pitch = dumb->pitch; + args->size = dumb->size; + gpu->dumbBuffers << dumb; + return 0; + } else if (request == DRM_IOCTL_MODE_DESTROY_DUMB) { + auto args = static_cast(arg); + auto it = std::find_if(gpu->dumbBuffers.begin(), gpu->dumbBuffers.end(), [args](const auto &buf){return buf->handle == args->handle;}); + if (it == gpu->dumbBuffers.end()) { + qWarning("buffer %u not found!", args->handle); + return -(errno = EINVAL); + } else { + gpu->dumbBuffers.erase(it); + return 0; + } + } else if (request == DRM_IOCTL_MODE_MAP_DUMB) { + auto args = static_cast(arg); + auto it = std::find_if(gpu->dumbBuffers.begin(), gpu->dumbBuffers.end(), [args](const auto &buf){return buf->handle == args->handle;}); + if (it == gpu->dumbBuffers.end()) { + qWarning("buffer %u not found!", args->handle); + return -(errno = EINVAL); + } else { + args->offset = reinterpret_cast((*it)->data); + return 0; + } + } + return -(errno = ENOTSUP); +} + +drmModeResPtr drmModeGetResources(int fd) +{ + GPU(fd, nullptr) + drmModeResPtr res = new drmModeRes; + + res->count_connectors = gpu->connectors.count(); + res->connectors = res->count_connectors ? new uint32_t[res->count_connectors] : nullptr; + int i = 0; + for (const auto &conn : qAsConst(gpu->connectors)) { + res->connectors[i++] = conn->id; + } + + res->count_encoders = gpu->encoders.count(); + res->encoders = res->count_encoders ? new uint32_t[res->count_encoders] : nullptr; + i = 0; + for (const auto &enc : qAsConst(gpu->encoders)) { + res->encoders[i++] = enc->id; + } + + res->count_crtcs = gpu->crtcs.count(); + res->crtcs = res->count_crtcs ? new uint32_t[res->count_crtcs] : nullptr; + i = 0; + for (const auto &crtc : qAsConst(gpu->crtcs)) { + res->crtcs[i++] = crtc->id; + } + + res->count_fbs = gpu->fbs.count(); + res->fbs = res->count_fbs ? new uint32_t[res->count_fbs] : nullptr; + i = 0; + for (const auto &fb : qAsConst(gpu->fbs)) { + res->fbs[i++] = fb->id; + } + + res->min_width = 0; + res->min_height = 0; + res->max_width = 2 << 14; + res->max_height = 2 << 14; + + gpu->resPtrs << res; + return res; +} + +int drmModeAddFB(int fd, uint32_t width, uint32_t height, uint8_t depth, + uint8_t bpp, uint32_t pitch, uint32_t bo_handle, + uint32_t *buf_id) +{ + Q_UNUSED(depth) + Q_UNUSED(bpp) + Q_UNUSED(pitch) + Q_UNUSED(bo_handle) + GPU(fd, EINVAL) + auto fb = new MockFb(gpu, width, height); + *buf_id = fb->id; + return 0; +} + +int drmModeAddFB2(int fd, uint32_t width, uint32_t height, + uint32_t pixel_format, const uint32_t bo_handles[4], + const uint32_t pitches[4], const uint32_t offsets[4], + uint32_t *buf_id, uint32_t flags) +{ + Q_UNUSED(pixel_format) + Q_UNUSED(bo_handles) + Q_UNUSED(pitches) + Q_UNUSED(offsets) + Q_UNUSED(flags) + GPU(fd, EINVAL) + auto fb = new MockFb(gpu, width, height); + *buf_id = fb->id; + return 0; +} + +int drmModeAddFB2WithModifiers(int fd, uint32_t width, uint32_t height, + uint32_t pixel_format, const uint32_t bo_handles[4], + const uint32_t pitches[4], const uint32_t offsets[4], + const uint64_t modifier[4], uint32_t *buf_id, + uint32_t flags) +{ + Q_UNUSED(pixel_format) + Q_UNUSED(bo_handles) + Q_UNUSED(pitches) + Q_UNUSED(offsets) + Q_UNUSED(modifier) + Q_UNUSED(flags) + GPU(fd, EINVAL) + if (!gpu->deviceCaps.contains(DRM_CAP_ADDFB2_MODIFIERS)) { + return -(errno = ENOTSUP); + } + auto fb = new MockFb(gpu, width, height); + *buf_id = fb->id; + return 0; +} + +int drmModeRmFB(int fd, uint32_t bufferId) +{ + GPU(fd, EINVAL) + auto it = std::find_if(gpu->fbs.begin(), gpu->fbs.end(), [bufferId](const auto &fb){return fb->id == bufferId;}); + if (it == gpu->fbs.end()) { + qWarning("invalid bufferId %u passed to drmModeRmFB", bufferId); + return EINVAL; + } else { + auto fb = *it; + gpu->fbs.erase(it); + for (const auto &plane : qAsConst(gpu->planes)) { + if (plane->nextFb == fb) { + plane->nextFb = nullptr; + } + if (plane->currentFb == fb) { + qWarning("current fb %u of plane %u got removed. Deactivating plane", bufferId, plane->id); + plane->setProp(QStringLiteral("CRTC_ID"), 0); + plane->setProp(QStringLiteral("FB_ID"), 0); + plane->currentFb = nullptr; + + auto crtc = gpu->findCrtc(plane->getProp(QStringLiteral("CRTC_ID"))); + Q_ASSERT(crtc); + crtc->setProp(QStringLiteral("ACTIVE"), 0); + qWarning("deactvating crtc %u", crtc->id); + + for (const auto &conn : qAsConst(gpu->connectors)) { + if (conn->getProp(QStringLiteral("CRTC_ID")) == crtc->id) { + conn->setProp(QStringLiteral("CRTC_ID"), 0); + qWarning("deactvating connector %u", conn->id); + } + } + } + } + delete fb; + return 0; + } +} + +drmModeCrtcPtr drmModeGetCrtc(int fd, uint32_t crtcId) +{ + GPU(fd, nullptr); + if (auto crtc = gpu->findCrtc(crtcId)) { + drmModeCrtcPtr c = new drmModeCrtc; + c->crtc_id = crtcId; + c->buffer_id = crtc->currentFb ? crtc->currentFb->id : 0; + c->gamma_size = crtc->gamma_size; + c->mode_valid = crtc->modeValid; + c->mode = crtc->mode; + c->x = 0; + c->y = 0; + c->width = crtc->mode.hdisplay; + c->height = crtc->mode.vdisplay; + gpu->drmCrtcs << c; + return c; + } else { + qWarning("invalid crtcId %u passed to drmModeGetCrtc", crtcId); + errno = EINVAL; + return nullptr; + } +} + +int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId, + uint32_t x, uint32_t y, uint32_t *connectors, int count, + drmModeModeInfoPtr mode) +{ + GPU(fd, -EINVAL); + auto crtc = gpu->findCrtc(crtcId); + if (!crtc) { + qWarning("invalid crtcId %u passed to drmModeSetCrtc", crtcId); + return -(errno = EINVAL); + } + auto oldModeBlob = crtc->getProp(QStringLiteral("MODE_ID")); + uint32_t modeBlob = 0; + if (mode) { + drmModeCreatePropertyBlob(fd, mode, sizeof(drmModeModeInfo), &modeBlob); + } + + auto req = drmModeAtomicAlloc(); + req->legacyEmulation = true; + drmModeAtomicAddProperty(req, crtcId, crtc->getPropId(QStringLiteral("MODE_ID")), modeBlob); + drmModeAtomicAddProperty(req, crtcId, crtc->getPropId(QStringLiteral("ACTIVE")), modeBlob && count); + QVector conns; + for (int i = 0; i < count; i++) { + conns << connectors[i]; + } + for (const auto &conn : qAsConst(gpu->connectors)) { + if (conns.contains(conn->id)) { + drmModeAtomicAddProperty(req, conn->id, conn->getPropId(QStringLiteral("CRTC_ID")), modeBlob ? crtc->id : 0); + conns.removeOne(conn->id); + } else if (conn->getProp(QStringLiteral("CRTC_ID")) == crtc->id) { + drmModeAtomicAddProperty(req, conn->id, conn->getPropId(QStringLiteral("CRTC_ID")), 0); + } + } + if (!conns.isEmpty()) { + for (const auto &c : qAsConst(conns)) { + qWarning("invalid connector %u passed to drmModeSetCrtc", c); + } + drmModeAtomicFree(req); + return -(errno = EINVAL); + } + drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_ID")), modeBlob && count ? crtc->id : 0); + drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_X")), x); + drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_Y")), y); + drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_W")), mode->hdisplay - x); + drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_H")), mode->vdisplay - y); + drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("FB_ID")), bufferId); + int result = drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); + drmModeAtomicFree(req); + if (result == 0) { + drmModeDestroyPropertyBlob(fd, oldModeBlob); + } + return result; +} + +int drmModeSetCursor(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height) +{ + GPU(fd, -EINVAL); + if (auto crtc = gpu->findCrtc(crtcId)) { + if (bo_handle != 0) { + auto it = std::find_if(gpu->dumbBuffers.constBegin(), gpu->dumbBuffers.constEnd(), [bo_handle](const auto &bo){return bo->handle == bo_handle;}); + if (it == gpu->dumbBuffers.constEnd()) { + qWarning("invalid bo_handle %u passed to drmModeSetCursor", bo_handle); + return -(errno = EINVAL); + } + crtc->cursorBo = (*it).get(); + } else { + crtc->cursorBo = nullptr; + } + crtc->cursorRect.setSize(QSize(width, height)); + return 0; + } else { + qWarning("invalid crtcId %u passed to drmModeSetCursor", crtcId); + return -(errno = EINVAL); + } +} + +int drmModeSetCursor2(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height, int32_t hot_x, int32_t hot_y) +{ + GPU(fd, -EINVAL); + // TODO ? + Q_UNUSED(crtcId) + Q_UNUSED(bo_handle) + Q_UNUSED(width) + Q_UNUSED(height) + Q_UNUSED(hot_x) + Q_UNUSED(hot_y) + return -(errno = ENOTSUP); +} + +int drmModeMoveCursor(int fd, uint32_t crtcId, int x, int y) +{ + GPU(fd, -EINVAL); + if (auto crtc = gpu->findCrtc(crtcId)) { + crtc->cursorRect.moveTo(x, y); + return 0; + } else { + qWarning("invalid crtcId %u passed to drmModeMoveCursor", crtcId); + return -(errno = EINVAL); + } +} + +drmModeEncoderPtr drmModeGetEncoder(int fd, uint32_t encoder_id) +{ + GPU(fd, nullptr); + auto it = std::find_if(gpu->encoders.constBegin(), gpu->encoders.constEnd(), [encoder_id](const auto &e){return e->id == encoder_id;}); + if (it == gpu->encoders.constEnd()) { + qWarning("invalid encoder_id %u passed to drmModeGetEncoder", encoder_id); + errno = EINVAL; + return nullptr; + } else { + auto encoder = *it; + drmModeEncoderPtr enc = new drmModeEncoder; + enc->encoder_id = encoder_id; + enc->crtc_id = encoder->crtc ? encoder->crtc->id : 0; + enc->encoder_type = 0; + enc->possible_crtcs = encoder->possible_crtcs; + enc->possible_clones = encoder->possible_clones; + + gpu->drmEncoders << enc; + return enc; + } +} + +drmModeConnectorPtr drmModeGetConnector(int fd, uint32_t connectorId) +{ + GPU(fd, nullptr); + if (auto conn = gpu->findConnector(connectorId)) { + drmModeConnectorPtr c = new drmModeConnector{}; + c->connector_id = conn->id; + c->connection = conn->connection; + c->connector_type = conn->type; + c->encoder_id = conn->encoder ? conn->encoder->id : 0; + c->count_encoders = conn->encoder ? 1 : 0; + c->encoders = c->count_encoders ? new uint32_t[1] : nullptr; + if (c->encoders) { + c->encoders[0] = conn->encoder->id; + } + c->count_modes = conn->modes.count(); + c->modes = c->count_modes ? new drmModeModeInfo[c->count_modes] : nullptr; + for (int i = 0; i < c->count_modes; i++) { + c->modes[i] = conn->modes[i]; + } + c->mmHeight = 900; + c->mmWidth = 1600; + c->subpixel = DRM_MODE_SUBPIXEL_HORIZONTAL_RGB; + + c->connector_type_id = DRM_MODE_CONNECTOR_DisplayPort;// ? + + // these are not used nor will they be + c->count_props = -1; + c->props = nullptr; + c->prop_values = nullptr; + + gpu->drmConnectors << c; + return c; + } else { + qWarning("invalid connectorId %u passed to drmModeGetConnector", connectorId); + errno = EINVAL; + return nullptr; + } +} + +drmModeConnectorPtr drmModeGetConnectorCurrent(int fd, uint32_t connector_id) +{ + return drmModeGetConnector(fd, connector_id); +} + +int drmModeCrtcSetGamma(int fd, uint32_t crtc_id, uint32_t size, uint16_t *red, uint16_t *green, uint16_t *blue) +{ + // TODO + Q_UNUSED(fd) + Q_UNUSED(crtc_id) + Q_UNUSED(size) + Q_UNUSED(red) + Q_UNUSED(green) + Q_UNUSED(blue) + return -(errno = ENOTSUP); +} + +int drmModePageFlip(int fd, uint32_t crtc_id, uint32_t fb_id, uint32_t flags, void *user_data) +{ + GPU(fd, -EINVAL); + auto crtc = gpu->findCrtc(crtc_id); + if (!crtc) { + qWarning("invalid crtc_id %u passed to drmModePageFlip", crtc_id); + return -(errno = EINVAL); + } + auto req = drmModeAtomicAlloc(); + req->legacyEmulation = true; + drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("FB_ID")), fb_id); + int result = drmModeAtomicCommit(fd, req, flags, user_data); + drmModeAtomicFree(req); + return result; +} + + +drmModePlaneResPtr drmModeGetPlaneResources(int fd) +{ + GPU(fd, nullptr); + drmModePlaneResPtr res = new drmModePlaneRes; + res->count_planes = gpu->planes.count(); + res->planes = res->count_planes ? new uint32_t[res->count_planes] : nullptr; + for (uint i = 0; i < res->count_planes; i++) { + res->planes[i] = gpu->planes[i]->id; + } + gpu->drmPlaneRes << res; + return res; +} + +drmModePlanePtr drmModeGetPlane(int fd, uint32_t plane_id) +{ + GPU(fd, nullptr); + if (auto plane = gpu->findPlane(plane_id)) { + drmModePlanePtr p = new drmModePlane; + p->plane_id = plane_id; + p->crtc_id = plane->getProp(QStringLiteral("CRTC_ID")); + p->crtc_x = plane->getProp(QStringLiteral("CRTC_X")); + p->crtc_y = plane->getProp(QStringLiteral("CRTC_Y")); + p->fb_id = plane->getProp(QStringLiteral("FB_ID")); + p->x = plane->getProp(QStringLiteral("SRC_X")); + p->y = plane->getProp(QStringLiteral("SRC_Y")); + p->possible_crtcs = plane->possibleCrtcs; + + // unused atm: + p->count_formats = 0; + p->formats = nullptr; + p->gamma_size = 0; + + gpu->drmPlanes << p; + return p; + } else { + qWarning("invalid plane_id %u passed to drmModeGetPlane", plane_id); + errno = EINVAL; + return nullptr; + } +} + +drmModePropertyPtr drmModeGetProperty(int fd, uint32_t propertyId) +{ + GPU(fd, nullptr); + for (const auto &obj : qAsConst(gpu->objects)) { + for (auto &prop : qAsConst(obj->props)) { + if (prop.id == propertyId) { + drmModePropertyPtr p = new drmModePropertyRes; + p->prop_id = prop.id; + p->flags = prop.flags; + auto arr = prop.name.toLocal8Bit(); + strcpy(p->name, arr.constData()); + + p->count_blobs = prop.flags & DRM_MODE_PROP_BLOB ? 1 : 0; + if (p->count_blobs) { + p->blob_ids = new uint32_t[1]; + p->blob_ids[0] = prop.value; + } else { + p->blob_ids = nullptr; + } + + p->count_enums = prop.enums.count(); + p->enums = new drm_mode_property_enum[p->count_enums]; + for (int i = 0; i < p->count_enums; i++) { + strcpy(p->enums[i].name, prop.enums[i].constData()); + p->enums[i].value = i; + } + + p->count_values = 1; + p->values = new uint64_t[1]; + p->values[0] = prop.value; + + gpu->drmProps << p; + return p; + } + } + } + qWarning("invalid propertyId %u passed to drmModeGetProperty", propertyId); + errno = EINVAL; + return nullptr; +} + +void drmModeFreeProperty(drmModePropertyPtr ptr) +{ + if (!ptr) { + return; + } + for (const auto &gpu : qAsConst(s_gpus)) { + if (gpu->drmProps.removeOne(ptr)) { + delete[] ptr->values; + delete[] ptr->blob_ids; + delete[] ptr->enums; + delete ptr; + return; + } + } +} + + + +drmModePropertyBlobPtr drmModeGetPropertyBlob(int fd, uint32_t blob_id) +{ + GPU(fd, nullptr); + if (blob_id == 0) { + return nullptr; + } + auto it = std::find_if(gpu->propertyBlobs.begin(), gpu->propertyBlobs.end(), [blob_id](const auto &blob) { + return blob->id == blob_id; + }); + if (it == gpu->propertyBlobs.end()) { + qWarning("invalid blob_id %u passed to drmModeGetPropertyBlob", blob_id); + errno = EINVAL; + return nullptr; + } else { + auto blob = new drmModePropertyBlobRes; + blob->id = (*it)->id; + blob->length = (*it)->size; + blob->data = malloc(blob->length); + memcpy(blob->data, (*it)->data, blob->length); + return blob; + } +} + +void drmModeFreePropertyBlob(drmModePropertyBlobPtr ptr) +{ + if (!ptr) { + return; + } + for (const auto &gpu : qAsConst(s_gpus)) { + if (gpu->drmPropertyBlobs.removeOne(ptr)) { + free(ptr->data); + delete ptr; + return; + } + } +} + +int drmModeConnectorSetProperty(int fd, uint32_t connector_id, uint32_t property_id, uint64_t value) +{ + return drmModeObjectSetProperty(fd, connector_id, DRM_MODE_OBJECT_CONNECTOR, property_id, value); +} + +static uint32_t getType(MockObject *obj) +{ + if (dynamic_cast(obj)) { + return DRM_MODE_OBJECT_CONNECTOR; + } else if (dynamic_cast(obj)) { + return DRM_MODE_OBJECT_CRTC; + } else if (dynamic_cast(obj)) { + return DRM_MODE_OBJECT_PLANE; + } else { + return DRM_MODE_OBJECT_ANY; + } +} + +drmModeObjectPropertiesPtr drmModeObjectGetProperties(int fd, uint32_t object_id, uint32_t object_type) +{ + GPU(fd, nullptr); + auto it = std::find_if(gpu->objects.constBegin(), gpu->objects.constEnd(), [object_id](const auto &obj){return obj->id == object_id;}); + if (it == gpu->objects.constEnd()) { + qWarning("invalid object_id %u passed to drmModeObjectGetProperties", object_id); + errno = EINVAL; + return nullptr; + } else { + auto obj = *it; + if (auto type = getType(obj); type != object_type) { + qWarning("wrong object_type %u passed to drmModeObjectGetProperties for object %u with type %u", object_type, object_id, type); + errno = EINVAL; + return nullptr; + } + QVector props; + bool deviceAtomic = gpu->clientCaps.contains(DRM_CLIENT_CAP_ATOMIC) && gpu->clientCaps[DRM_CLIENT_CAP_ATOMIC]; + for (const auto &prop : qAsConst(obj->props)) { + if (deviceAtomic || !(prop.flags & DRM_MODE_PROP_ATOMIC)) { + props << prop; + } + } + drmModeObjectPropertiesPtr p = new drmModeObjectProperties; + p->count_props = props.count(); + p->props = new uint32_t[p->count_props]; + p->prop_values = new uint64_t[p->count_props]; + int i = 0; + for (const auto &prop : qAsConst(props)) { + p->props[i] = prop.id; + p->prop_values[i] = prop.value; + i++; + } + gpu->drmObjectProperties << p; + return p; + } +} + +void drmModeFreeObjectProperties(drmModeObjectPropertiesPtr ptr) +{ + for (const auto &gpu : qAsConst(s_gpus)) { + if (gpu->drmObjectProperties.removeOne(ptr)) { + delete[] ptr->props; + delete[] ptr->prop_values; + delete ptr; + return; + } + } +} + +int drmModeObjectSetProperty(int fd, uint32_t object_id, uint32_t object_type, uint32_t property_id, uint64_t value) +{ + GPU(fd, -EINVAL); + auto it = std::find_if(gpu->objects.constBegin(), gpu->objects.constEnd(), [object_id](const auto &obj){return obj->id == object_id;}); + if (it == gpu->objects.constEnd()) { + qWarning("invalid object_id %u passed to drmModeObjectSetProperty", object_id); + return -(errno = EINVAL); + } else { + auto obj = *it; + if (auto type = getType(obj); type != object_type) { + qWarning("wrong object_type %u passed to drmModeObjectSetProperty for object %u with type %u", object_type, object_id, type); + return -(errno = EINVAL); + } + auto req = drmModeAtomicAlloc(); + req->legacyEmulation = true; + drmModeAtomicAddProperty(req, object_id, property_id, value); + int result = drmModeAtomicCommit(fd, req, 0, nullptr); + drmModeAtomicFree(req); + return result; + } +} + +static QVector s_atomicReqs; + +drmModeAtomicReqPtr drmModeAtomicAlloc(void) +{ + auto req = new drmModeAtomicReq; + s_atomicReqs << req; + return req; +} + +void drmModeAtomicFree(drmModeAtomicReqPtr req) +{ + s_atomicReqs.removeOne(req); + delete req; +} + +int drmModeAtomicAddProperty(drmModeAtomicReqPtr req, uint32_t object_id, uint32_t property_id, uint64_t value) +{ + if (!req) { + return -(errno = EINVAL); + } + Prop p; + p.obj = object_id; + p.prop = property_id; + p.value = value; + req->props << p; + return req->props.count(); +} + +static bool checkIfEqual(const drmModeModeInfo &one, const drmModeModeInfo &two) +{ + return one.clock == two.clock + && one.hdisplay == two.hdisplay + && one.hsync_start == two.hsync_start + && one.hsync_end == two.hsync_end + && one.htotal == two.htotal + && one.hskew == two.hskew + && one.vdisplay == two.vdisplay + && one.vsync_start == two.vsync_start + && one.vsync_end == two.vsync_end + && one.vtotal == two.vtotal + && one.vscan == two.vscan + && one.vrefresh == two.vrefresh; +} + +int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req, uint32_t flags, void *user_data) +{ + GPU(fd, -EINVAL); + if (!req->legacyEmulation && (!gpu->clientCaps.contains(DRM_CLIENT_CAP_ATOMIC) || !gpu->clientCaps[DRM_CLIENT_CAP_ATOMIC])) { + qWarning("drmModeAtomicCommit requires the atomic capability"); + return -(errno = EINVAL); + } + + // verify flags + if ((flags & DRM_MODE_ATOMIC_NONBLOCK) && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { + qWarning() << "NONBLOCK and MODESET are not allowed together"; + return -(errno = EINVAL); + } else if ((flags & DRM_MODE_ATOMIC_TEST_ONLY) && (flags & DRM_MODE_PAGE_FLIP_EVENT)) { + qWarning() << "TEST_ONLY and PAGE_FLIP_EVENT are not allowed together"; + return -(errno = EINVAL); + } else if (flags & DRM_MODE_PAGE_FLIP_ASYNC) { + qWarning() << "PAGE_FLIP_ASYNC is currently not supported with AMS"; + return -(errno = EINVAL); + } + + QVector connCopies; + for (const auto &conn : qAsConst(gpu->connectors)) { + connCopies << *conn; + } + QVector crtcCopies; + for (const auto &crtc : qAsConst(gpu->crtcs)) { + crtcCopies << *crtc; + } + QVector planeCopies; + for (const auto &plane : qAsConst(gpu->planes)) { + planeCopies << *plane; + } + + QVector objects; + for (int i = 0; i < connCopies.count(); i++) { + objects << &connCopies[i]; + } + for (int i = 0; i < crtcCopies.count(); i++) { + objects << &crtcCopies[i]; + } + for (int i = 0; i < planeCopies.count(); i++) { + objects << &planeCopies[i]; + } + + // apply changes to the copies + for (int i = 0; i < req->props.count(); i++) { + auto p = req->props[i]; + auto it = std::find_if(objects.constBegin(), objects.constEnd(), [p](const auto &obj){return obj->id == p.obj;}); + if (it == objects.constEnd()) { + qWarning("Object %u in atomic request not found!", p.obj); + return -(errno = EINVAL); + } + auto &obj = *it; + if (obj->id == p.obj) { + auto prop = std::find_if(obj->props.begin(), obj->props.end(), [p](const auto &prop){return prop.id == p.prop;}); + if (prop == obj->props.end()) { + qWarning("Property %u in atomic request for object %u not found!", p.prop, p.obj); + return -(errno = EINVAL); + } + if (prop->value != p.value) { + if (!(flags & DRM_MODE_ATOMIC_ALLOW_MODESET) && (prop->name == QStringLiteral("CRTC_ID") || prop->name == QStringLiteral("ACTIVE"))) { + qWarning("Atomic request without DRM_MODE_ATOMIC_ALLOW_MODESET tries to do a modeset with object %u", obj->id); + return -(errno = EINVAL); + } + if (prop->flags & DRM_MODE_PROP_BLOB) { + auto blobExists = gpu->getBlob(p.value) != nullptr; + if (blobExists != (p.value > 0)) { + qWarning("Atomic request tries to set property %s on obj %u to invalid blob id %lu", qPrintable(prop->name), obj->id, p.value); + return -(errno = EINVAL); + } + } + prop->value = p.value; + } + } + } + + // check if the desired changes are allowed + struct Pipeline { + MockCrtc *crtc; + QVector conns; + MockPlane *primaryPlane = nullptr; + }; + QVector pipelines; + for (int i = 0; i < crtcCopies.count(); i++) { + if (crtcCopies[i].getProp(QStringLiteral("ACTIVE"))) { + auto blob = gpu->getBlob(crtcCopies[i].getProp(QStringLiteral("MODE_ID"))); + if (!blob) { + qWarning("Atomic request tries to enable CRTC %u without a mode", crtcCopies[i].id); + return -(errno = EINVAL); + } else if (blob->size != sizeof(drmModeModeInfo)) { + qWarning("Atomic request tries to enable CRTC %u with an invalid mode blob", crtcCopies[i].id); + return -(errno = EINVAL); + } + Pipeline pipeline; + pipeline.crtc = &crtcCopies[i]; + pipelines << pipeline; + } + } + for (int i = 0; i < connCopies.count(); i++) { + if (auto crtc = connCopies[i].getProp(QStringLiteral("CRTC_ID"))) { + bool found = false; + for (int p = 0; p < pipelines.count(); p++) { + if (pipelines[p].crtc->id == crtc) { + pipelines[p].conns << &connCopies[i]; + found = true; + break; + } + } + if (!found) { + qWarning("CRTC_ID of connector %u points to inactive or wrong crtc", connCopies[i].id); + return -(errno = EINVAL); + } + } + } + for (int i = 0; i < planeCopies.count(); i++) { + if (auto crtc = planeCopies[i].getProp(QStringLiteral("CRTC_ID"))) { + bool found = false; + for (int p = 0; p < pipelines.count(); p++) { + if (pipelines[p].crtc->id == crtc) { + if (pipelines[p].primaryPlane) { + qWarning("crtc %u has more than one primary planes assigned: %u and %u", pipelines[p].crtc->id, pipelines[p].primaryPlane->id, planeCopies[i].id); + return -(errno = EINVAL); + } else if (!(planeCopies[i].possibleCrtcs & (1 << pipelines[p].crtc->pipeIndex))) { + qWarning("crtc %u is not suitable for primary plane %u", pipelines[p].crtc->id, planeCopies[i].id); + return -(errno = EINVAL); + } else { + pipelines[p].primaryPlane = &planeCopies[i]; + found = true; + break; + } + } + } + if (!found) { + qWarning("CRTC_ID of plane %u points to inactive or wrong crtc", planeCopies[i].id); + return -(errno = EINVAL); + } + auto fbId = planeCopies[i].getProp(QStringLiteral("FB_ID")); + if (!fbId) { + qWarning("FB_ID of active plane %u is 0", planeCopies[i].id); + return -(errno = EINVAL); + } + auto it = std::find_if(gpu->fbs.constBegin(), gpu->fbs.constEnd(), [fbId](auto fb){return fb->id == fbId;}); + if (it == gpu->fbs.constEnd()) { + qWarning("FB_ID %lu of active plane %u is invalid", fbId, planeCopies[i].id); + return -(errno = EINVAL); + } + planeCopies[i].nextFb = *it; + } else { + planeCopies[i].nextFb = nullptr; + } + } + for (const auto &p : qAsConst(pipelines)) { + if (p.conns.isEmpty()) { + qWarning("Active crtc %u has no assigned connectors", p.crtc->id); + return -(errno = EINVAL); + } else if (!p.primaryPlane) { + qWarning("Active crtc %u has no assigned primary plane", p.crtc->id); + return -(errno = EINVAL); + } else { + drmModeModeInfo mode = *static_cast(gpu->getBlob(p.crtc->getProp(QStringLiteral("MODE_ID")))->data); + for (const auto &conn : p.conns) { + bool modeFound = std::find_if(conn->modes.constBegin(), conn->modes.constEnd(), [mode](const auto &m){ + return checkIfEqual(mode, m); + }) != conn->modes.constEnd(); + if (!modeFound) { + qWarning("mode on crtc %u is incompatible with connector %u", p.crtc->id, conn->id); + return -(errno = EINVAL); + } + } + } + } + + // if wanted, apply them + + if (!(flags & DRM_MODE_ATOMIC_TEST_ONLY)) { + for (auto &conn : qAsConst(gpu->connectors)) { + auto it = std::find_if(connCopies.constBegin(), connCopies.constEnd(), [conn](auto c){return c.id == conn->id;}); + if (it == connCopies.constEnd()) { + qCritical("implementation error: can't find connector %u", conn->id); + return -(errno = EINVAL); + } + *conn = *it; + } + for (auto &crtc : qAsConst(gpu->crtcs)) { + auto it = std::find_if(crtcCopies.constBegin(), crtcCopies.constEnd(), [crtc](auto c){return c.id == crtc->id;}); + if (it == crtcCopies.constEnd()) { + qCritical("implementation error: can't find crtc %u", crtc->id); + return -(errno = EINVAL); + } + *crtc = *it; + } + for (auto &plane : qAsConst(gpu->planes)) { + auto it = std::find_if(planeCopies.constBegin(), planeCopies.constEnd(), [plane](auto c){return c.id == plane->id;}); + if (it == planeCopies.constEnd()) { + qCritical("implementation error: can't find plane %u", plane->id); + return -(errno = EINVAL); + } + *plane = *it; + } + + if (flags & DRM_MODE_PAGE_FLIP_EVENT) { + // TODO + Q_UNUSED(user_data) + } + } + + return 0; +} + + +int drmModeCreatePropertyBlob(int fd, const void *data, size_t size, uint32_t *id) +{ + GPU(fd, -EINVAL); + if (!data || !size || !id) { + return -(errno = EINVAL); + } + auto blob = std::make_unique(gpu, data, size); + *id = blob->id; + gpu->propertyBlobs.push_back(std::move(blob)); + return 0; +} + +int drmModeDestroyPropertyBlob(int fd, uint32_t id) +{ + GPU(fd, -EINVAL); + auto it = std::remove_if(gpu->propertyBlobs.begin(), gpu->propertyBlobs.end(), [id](const auto &blob) { + return blob->id == id; + }); + if (it == gpu->propertyBlobs.end()) { + return -(errno = EINVAL); + } else { + gpu->propertyBlobs.erase(it, gpu->propertyBlobs.end()); + return 0; + } +} + +int drmModeCreateLease(int fd, const uint32_t *objects, int num_objects, int flags, uint32_t *lessee_id) +{ + // TODO? + Q_UNUSED(fd) + Q_UNUSED(objects) + Q_UNUSED(num_objects) + Q_UNUSED(flags) + Q_UNUSED(lessee_id) + return -(errno = ENOTSUP); +} + +drmModeLesseeListPtr drmModeListLessees(int fd) +{ + // TODO + Q_UNUSED(fd) + return nullptr; +} + +drmModeObjectListPtr drmModeGetLease(int fd) +{ + // TODO? + Q_UNUSED(fd) + return nullptr; +} + +int drmModeRevokeLease(int fd, uint32_t lessee_id) +{ + // TODO? + Q_UNUSED(fd) + Q_UNUSED(lessee_id) + return -(errno = ENOTSUP); +} + +void drmModeFreeResources(drmModeResPtr ptr) +{ + for (const auto &gpu : qAsConst(s_gpus)) { + if (gpu->resPtrs.removeOne(ptr)) { + delete[] ptr->connectors; + delete[] ptr->crtcs; + delete[] ptr->encoders; + delete[] ptr->fbs; + delete ptr; + } + } +} + +void drmModeFreePlaneResources(drmModePlaneResPtr ptr) +{ + for (const auto &gpu : qAsConst(s_gpus)) { + if (gpu->drmPlaneRes.removeOne(ptr)) { + delete[] ptr->planes; + delete ptr; + } + } +} + +void drmModeFreeCrtc(drmModeCrtcPtr ptr) +{ + for (const auto &gpu : qAsConst(s_gpus)) { + if (gpu->drmCrtcs.removeOne(ptr)) { + delete ptr; + return; + } + } + Q_UNREACHABLE(); +} + +void drmModeFreeConnector(drmModeConnectorPtr ptr) +{ + for (const auto &gpu : qAsConst(s_gpus)) { + if (gpu->drmConnectors.removeOne(ptr)) { + delete[] ptr->encoders; + delete[] ptr->props; + delete[] ptr->prop_values; + delete ptr; + return; + } + } + Q_UNREACHABLE(); +} + +void drmModeFreeEncoder(drmModeEncoderPtr ptr) +{ + for (const auto &gpu : qAsConst(s_gpus)) { + if (gpu->drmEncoders.removeOne(ptr)) { + delete ptr; + return; + } + } + Q_UNREACHABLE(); +} + +void drmModeFreePlane(drmModePlanePtr ptr) +{ + for (const auto &gpu : qAsConst(s_gpus)) { + if (gpu->drmPlanes.removeOne(ptr)) { + delete ptr; + return; + } + } + Q_UNREACHABLE(); +} diff --git a/autotests/drm/mock_drm.h b/autotests/drm/mock_drm.h new file mode 100644 index 0000000000..a03151eaff --- /dev/null +++ b/autotests/drm/mock_drm.h @@ -0,0 +1,205 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2022 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#pragma once + +#include +#include + +#include +#include +#include +#include + +class MockGpu; +class MockFb; +class MockCrtc; +class MockEncoder; +class MockObject; +class MockDumbBuffer; +class MockPlane; + +class MockProperty { +public: + MockProperty(MockObject *obj, QString name, uint64_t initialValue, uint32_t flags, QVector enums = {}); + ~MockProperty() = default; + + MockObject *obj; + uint32_t id; + uint32_t flags; + QString name; + uint64_t value; + QVector enums; +}; + +class MockPropertyBlob { +public: + MockPropertyBlob(MockGpu *gpu, const void *data, size_t size); + ~MockPropertyBlob(); + + MockGpu *gpu; + uint32_t id; + void *data; + size_t size; +}; + +class MockObject { +public: + MockObject(MockGpu *gpu); + virtual ~MockObject(); + + uint64_t getProp(const QString &propName) const; + void setProp(const QString &propName, uint64_t value); + + uint32_t getPropId(const QString &propName) const; + + uint32_t id; + QVector props; + MockGpu *gpu; +}; + +class MockConnector : public MockObject { +public: + MockConnector(MockGpu *gpu, bool nonDesktop = false); + MockConnector(const MockConnector &obj) = default; + ~MockConnector() = default; + + void addMode(uint32_t width, uint32_t height, float refreshRate, bool preferred = false); + + drmModeConnection connection; + uint32_t type; + std::shared_ptr encoder; + QVector modes; +}; + +class MockEncoder : public MockObject { +public: + MockEncoder(MockGpu *gpu, uint32_t possible_crtcs); + MockEncoder(const MockEncoder &obj) = default; + ~MockEncoder() = default; + + MockCrtc *crtc = nullptr; + uint32_t possible_crtcs; + uint32_t possible_clones = 0; +}; + +class MockCrtc : public MockObject { +public: + MockCrtc(MockGpu *gpu, const std::shared_ptr &legacyPlane, int pipeIndex, int gamma_size = 255); + MockCrtc(const MockCrtc &obj) = default; + ~MockCrtc() = default; + + int pipeIndex; + int gamma_size; + drmModeModeInfo mode; + bool modeValid = true; + MockFb *currentFb = nullptr; + MockFb *nextFb = nullptr; + QRect cursorRect; + MockDumbBuffer *cursorBo = nullptr; + std::shared_ptr legacyPlane; +}; + +enum class PlaneType { + Primary, + Overlay, + Cursor +}; + +class MockPlane : public MockObject { +public: + MockPlane(MockGpu *gpu, PlaneType type, int crtcIndex); + MockPlane(const MockPlane &obj) = default; + ~MockPlane() = default; + + MockFb *currentFb = nullptr; + MockFb *nextFb = nullptr; + int possibleCrtcs; + PlaneType type; +}; + +class MockFb { +public: + MockFb(MockGpu *gpu, uint32_t width, uint32_t height); + ~MockFb(); + + uint32_t id; + uint32_t width, height; + MockGpu *gpu; +}; + +class MockDumbBuffer { +public: + MockDumbBuffer(MockGpu *gpu, uint32_t width, uint32_t height, uint32_t bpp); + ~MockDumbBuffer(); + + uint32_t handle; + uint32_t pitch; + uint64_t size; + void *data; + MockGpu *gpu; +}; + +struct Prop { + uint32_t obj; + uint32_t prop; + uint64_t value; +}; + +struct _drmModeAtomicReq { + bool legacyEmulation = false; + QVector props; +}; + +#define MOCKDRM_DEVICE_CAP_ATOMIC 0xFF + +class MockGpu { +public: + MockGpu(int fd, int numCrtcs, int gammaSize = 255); + ~MockGpu(); + + MockConnector *findConnector(uint32_t id) const; + MockCrtc *findCrtc(uint32_t id) const; + MockPlane *findPlane(uint32_t id) const; + MockPropertyBlob *getBlob(uint32_t id) const; + + void flipPage(uint32_t crtcId); + + int fd; + QByteArray name = QByteArrayLiteral("mock"); + QMap clientCaps; + QMap deviceCaps; + + uint32_t idCounter = 1; + QVector objects; + + QVector> connectors; + QVector drmConnectors; + + QVector> encoders; + QVector drmEncoders; + + QVector> crtcs; + QVector drmCrtcs; + + QVector> planes; + QVector drmPlanes; + + QVector fbs; + QVector> dumbBuffers; + std::vector> propertyBlobs; + + QVector resPtrs; + QVector drmProps; + QVector drmPropertyBlobs; + QVector drmObjectProperties; + QVector drmPlaneRes; +}; + + + diff --git a/src/backends/drm/drm_backend.cpp b/src/backends/drm/drm_backend.cpp index f0a3523210..59834eb347 100644 --- a/src/backends/drm/drm_backend.cpp +++ b/src/backends/drm/drm_backend.cpp @@ -33,6 +33,7 @@ // Qt #include #include +#include // system #include #include diff --git a/src/backends/drm/drm_dumb_buffer.cpp b/src/backends/drm/drm_dumb_buffer.cpp index 86d01d0381..b69184be53 100644 --- a/src/backends/drm/drm_dumb_buffer.cpp +++ b/src/backends/drm/drm_dumb_buffer.cpp @@ -44,11 +44,15 @@ bool DrmDumbBuffer::map(QImage::Format format) if (drmIoctl(m_gpu->fd(), DRM_IOCTL_MODE_MAP_DUMB, &mapArgs) != 0) { return false; } +#ifdef KWIN_UNIT_TEST + m_memory = reinterpret_cast(mapArgs.offset); +#else void *address = mmap(nullptr, m_bufferSize, PROT_WRITE, MAP_SHARED, m_gpu->fd(), mapArgs.offset); if (address == MAP_FAILED) { return false; } m_memory = address; +#endif m_image = std::make_unique((uchar *)m_memory, m_size.width(), m_size.height(), m_strides[0], format); return !m_image->isNull(); }