File indexing completed on 2024-05-05 17:35:39

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "mock_drm.h"
0010 
0011 #include <errno.h>
0012 extern "C" {
0013 #include <libxcvt/libxcvt.h>
0014 }
0015 #include <math.h>
0016 
0017 #include <QMap>
0018 #include <QDebug>
0019 
0020 // Mock impls
0021 
0022 static QMap<int, MockGpu*> s_gpus;
0023 
0024 static MockGpu *getGpu(int fd)
0025 {
0026     return s_gpus[fd];
0027 }
0028 
0029 MockGpu::MockGpu(int fd, int numCrtcs, int gammaSize)
0030     : fd(fd)
0031 {
0032     s_gpus.insert(fd, this);
0033     for (int i = 0; i < numCrtcs; i++) {
0034         const auto &plane = std::make_shared<MockPlane>(this, PlaneType::Primary, i);
0035         crtcs << std::make_shared<MockCrtc>(this, plane, i, gammaSize);
0036         planes << plane;
0037     }
0038     deviceCaps.insert(MOCKDRM_DEVICE_CAP_ATOMIC, 1);
0039     deviceCaps.insert(DRM_CAP_DUMB_BUFFER, 1);
0040     deviceCaps.insert(DRM_CAP_PRIME, DRM_PRIME_CAP_IMPORT | DRM_PRIME_CAP_EXPORT);
0041     deviceCaps.insert(DRM_CAP_ASYNC_PAGE_FLIP, 0);
0042     deviceCaps.insert(DRM_CAP_ADDFB2_MODIFIERS, 1);
0043 }
0044 
0045 MockGpu::~MockGpu()
0046 {
0047     s_gpus.remove(fd);
0048 }
0049 
0050 MockPropertyBlob *MockGpu::getBlob(uint32_t id) const
0051 {
0052     auto it = std::find_if(propertyBlobs.begin(), propertyBlobs.end(), [id](const auto &propBlob) {
0053         return propBlob->id == id;
0054     });
0055     return it == propertyBlobs.end() ? nullptr : it->get();
0056 }
0057 
0058 MockConnector *MockGpu::findConnector(uint32_t id) const
0059 {
0060     auto it = std::find_if(connectors.constBegin(), connectors.constEnd(), [id](const auto &c){return c->id == id;});
0061     return it == connectors.constEnd() ? nullptr : (*it).get();
0062 }
0063 
0064 MockCrtc *MockGpu::findCrtc(uint32_t id) const
0065 {
0066     auto it = std::find_if(crtcs.constBegin(), crtcs.constEnd(), [id](const auto &c){return c->id == id;});
0067     return it == crtcs.constEnd() ? nullptr : (*it).get();
0068 }
0069 
0070 MockPlane *MockGpu::findPlane(uint32_t id) const
0071 {
0072     auto it = std::find_if(planes.constBegin(), planes.constEnd(), [id](const auto &p){return p->id == id;});
0073     return it == planes.constEnd() ? nullptr : (*it).get();
0074 }
0075 
0076 void MockGpu::flipPage(uint32_t crtcId)
0077 {
0078     auto crtc = findCrtc(crtcId);
0079     Q_ASSERT(crtc);
0080     for (const auto &plane : std::as_const(planes)) {
0081         if (plane->getProp(QStringLiteral("CRTC_ID")) == crtc->id) {
0082             plane->currentFb = plane->nextFb;
0083         }
0084     }
0085     // TODO page flip event?
0086 }
0087 
0088 //
0089 
0090 MockObject::MockObject(MockGpu *gpu)
0091     : id(gpu->idCounter++)
0092     , gpu(gpu)
0093 {
0094     gpu->objects << this;
0095 }
0096 
0097 MockObject::~MockObject()
0098 {
0099     gpu->objects.removeOne(this);
0100 }
0101 
0102 uint64_t MockObject::getProp(const QString &propName) const
0103 {
0104     for (const auto &prop : std::as_const(props)) {
0105         if (prop.name == propName) {
0106             return prop.value;
0107         }
0108     }
0109     Q_UNREACHABLE();
0110 }
0111 
0112 void MockObject::setProp(const QString &propName, uint64_t value)
0113 {
0114     for (auto &prop : props) {
0115         if (prop.name == propName) {
0116             prop.value = value;
0117             return;
0118         }
0119     }
0120     Q_UNREACHABLE();
0121 }
0122 
0123 uint32_t MockObject::getPropId(const QString &propName) const
0124 {
0125     for (const auto &prop : std::as_const(props)) {
0126         if (prop.name == propName) {
0127             return prop.id;
0128         }
0129     }
0130     Q_UNREACHABLE();
0131 }
0132 
0133 //
0134 
0135 MockProperty::MockProperty(MockObject *obj, QString name, uint64_t initialValue, uint32_t flags, QVector<QByteArray> enums)
0136     : obj(obj)
0137     , id(obj->gpu->idCounter++)
0138     , flags(flags)
0139     , name(name)
0140     , value(initialValue)
0141     , enums(enums)
0142 {
0143     qDebug("Added property %s with id %u to object %u", qPrintable(name), id, obj->id);
0144 }
0145 
0146 MockPropertyBlob::MockPropertyBlob(MockGpu *gpu, const void *d, size_t size)
0147     : gpu(gpu)
0148     , id(gpu->idCounter++)
0149     , data(malloc(size))
0150     , size(size)
0151 {
0152     memcpy(data, d, size);
0153 }
0154 
0155 MockPropertyBlob::~MockPropertyBlob()
0156 {
0157     free(data);
0158 }
0159 
0160 //
0161 
0162 #define addProp(name, value, flags) props << MockProperty(this, QStringLiteral(name), value, flags)
0163 
0164 MockConnector::MockConnector(MockGpu *gpu, bool nonDesktop)
0165     : MockObject(gpu)
0166     , connection(DRM_MODE_CONNECTED)
0167     , type(DRM_MODE_CONNECTOR_DisplayPort)
0168     , encoder(std::make_shared<MockEncoder>(gpu, 0xFF))
0169 {
0170     gpu->encoders << encoder;
0171     addProp("CRTC_ID", 0, DRM_MODE_PROP_ATOMIC);
0172 
0173     addProp("Subpixel", DRM_MODE_SUBPIXEL_UNKNOWN, DRM_MODE_PROP_IMMUTABLE);
0174     addProp("non-desktop", nonDesktop, DRM_MODE_PROP_IMMUTABLE);
0175     addProp("vrr_capable", 0, DRM_MODE_PROP_IMMUTABLE);
0176 
0177     addProp("DPMS", DRM_MODE_DPMS_OFF, 0);
0178     addProp("EDID", 0, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE);
0179 
0180     addMode(1920, 1080, 60.0);
0181 }
0182 
0183 void MockConnector::addMode(uint32_t width, uint32_t height, float refreshRate, bool preferred)
0184 {
0185     auto modeInfo = libxcvt_gen_mode_info(width, height, refreshRate, false, false);
0186 
0187     drmModeModeInfo mode{
0188         .clock = uint32_t(modeInfo->dot_clock),
0189         .hdisplay = uint16_t(modeInfo->hdisplay),
0190         .hsync_start = modeInfo->hsync_start,
0191         .hsync_end = modeInfo->hsync_end,
0192         .htotal = modeInfo->htotal,
0193         .hskew = 0,
0194         .vdisplay = uint16_t(modeInfo->vdisplay),
0195         .vsync_start = modeInfo->vsync_start,
0196         .vsync_end = modeInfo->vsync_end,
0197         .vtotal = modeInfo->vtotal,
0198         .vscan = 1,
0199         .vrefresh = uint32_t(modeInfo->vrefresh),
0200         .flags = modeInfo->mode_flags,
0201         .type = DRM_MODE_TYPE_DRIVER,
0202     };
0203     if (preferred) {
0204         mode.type |= DRM_MODE_TYPE_PREFERRED;
0205     }
0206     sprintf(mode.name, "%dx%d@%d", width, height, mode.vrefresh);
0207 
0208     modes.push_back(mode);
0209     free(modeInfo);
0210 }
0211 
0212 //
0213 
0214 MockCrtc::MockCrtc(MockGpu *gpu, const std::shared_ptr<MockPlane> &legacyPlane, int pipeIndex, int gamma_size)
0215     : MockObject(gpu)
0216     , pipeIndex(pipeIndex)
0217     , gamma_size(gamma_size)
0218     , legacyPlane(legacyPlane)
0219 {
0220     addProp("MODE_ID", 0, DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_BLOB);
0221     addProp("ACTIVE", 0, DRM_MODE_PROP_ATOMIC);
0222     addProp("GAMMA_LUT", 0, DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_BLOB);
0223     addProp("GAMMA_LUT_SIZE", gamma_size, DRM_MODE_PROP_ATOMIC);
0224 }
0225 
0226 
0227 //
0228 
0229 MockPlane::MockPlane(MockGpu *gpu, PlaneType type, int crtcIndex)
0230     : MockObject(gpu)
0231     , possibleCrtcs(1 << crtcIndex)
0232     , type(type)
0233 {
0234     props << MockProperty(this, QStringLiteral("type"), static_cast<uint64_t>(type), DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_ENUM | DRM_MODE_PROP_BITMASK,
0235                             {QByteArrayLiteral("Primary"), QByteArrayLiteral("Overlay"), QByteArrayLiteral("Cursor")});
0236     addProp("FB_ID", 0, DRM_MODE_PROP_ATOMIC);
0237     addProp("CRTC_ID", 0, DRM_MODE_PROP_ATOMIC);
0238     addProp("CRTC_X", 0, DRM_MODE_PROP_ATOMIC);
0239     addProp("CRTC_Y", 0, DRM_MODE_PROP_ATOMIC);
0240     addProp("CRTC_W", 0, DRM_MODE_PROP_ATOMIC);
0241     addProp("CRTC_H", 0, DRM_MODE_PROP_ATOMIC);
0242     addProp("SRC_X", 0, DRM_MODE_PROP_ATOMIC);
0243     addProp("SRC_Y", 0, DRM_MODE_PROP_ATOMIC);
0244     addProp("SRC_W", 0, DRM_MODE_PROP_ATOMIC);
0245     addProp("SRC_H", 0, DRM_MODE_PROP_ATOMIC);
0246 }
0247 
0248 //
0249 
0250 MockEncoder::MockEncoder(MockGpu* gpu, uint32_t possible_crtcs)
0251     : MockObject(gpu)
0252     , possible_crtcs(possible_crtcs)
0253 {
0254 }
0255 
0256 
0257 //
0258 
0259 MockFb::MockFb(MockGpu *gpu, uint32_t width, uint32_t height)
0260     : id(gpu->idCounter++)
0261     , width(width)
0262     , height(height)
0263     , gpu(gpu)
0264 {
0265     gpu->fbs << this;
0266 }
0267 
0268 MockFb::~MockFb()
0269 {
0270     gpu->fbs.removeOne(this);
0271 }
0272 
0273 //
0274 
0275 MockDumbBuffer::MockDumbBuffer(MockGpu *gpu, uint32_t width, uint32_t height, uint32_t bpp)
0276     : handle(gpu->idCounter++)
0277     , pitch(width * ceil(bpp / 8.0))
0278     , data(height * pitch)
0279     , gpu(gpu)
0280 {
0281 }
0282 
0283 // drm functions
0284 
0285 #define GPU(fd, error) auto gpu = getGpu(fd);\
0286 if (!gpu) {\
0287     qWarning("invalid fd %d", fd);\
0288     errno = EINVAL;\
0289     return error;\
0290 }
0291 
0292 drmVersionPtr drmGetVersion(int fd)
0293 {
0294     GPU(fd, nullptr);
0295     drmVersionPtr ptr = new drmVersion;
0296     ptr->name = new char[gpu->name.size() + 1];
0297     strcpy(ptr->name, gpu->name.data());
0298     return ptr;
0299 }
0300 
0301 void drmFreeVersion(drmVersionPtr ptr)
0302 {
0303     Q_ASSERT(ptr);
0304     delete[] ptr->name;
0305     delete ptr;
0306 }
0307 
0308 int drmSetClientCap(int fd, uint64_t capability, uint64_t value)
0309 {
0310     GPU(fd, -EINVAL);
0311     if (capability == DRM_CLIENT_CAP_ATOMIC) {
0312         if (!gpu->deviceCaps[MOCKDRM_DEVICE_CAP_ATOMIC]) {
0313             return -(errno = ENOTSUP);
0314         }
0315         qDebug("Setting DRM_CLIENT_CAP_ATOMIC to %lu", value);
0316     }
0317     gpu->clientCaps.insert(capability, value);
0318     return 0;
0319 }
0320 
0321 int drmGetCap(int fd, uint64_t capability, uint64_t *value)
0322 {
0323     GPU(fd, -EINVAL);
0324     if (gpu->deviceCaps.contains(capability)) {
0325         *value = gpu->deviceCaps[capability];
0326         return 0;
0327     }
0328     qDebug("Could not find capability %lu", capability);
0329     return -(errno = EINVAL);
0330 }
0331 
0332 int drmHandleEvent(int fd, drmEventContextPtr evctx)
0333 {
0334     GPU(fd, -EINVAL);
0335     return -(errno = ENOTSUP);
0336 }
0337 
0338 int drmIoctl(int fd, unsigned long request, void *arg)
0339 {
0340     GPU(fd, -EINVAL);
0341     if (request == DRM_IOCTL_MODE_CREATE_DUMB) {
0342         auto args = static_cast<drm_mode_create_dumb*>(arg);
0343         auto dumb = std::make_shared<MockDumbBuffer>(gpu, args->width, args->height, args->bpp);
0344         args->handle = dumb->handle;
0345         args->pitch = dumb->pitch;
0346         args->size = dumb->data.size();
0347         gpu->dumbBuffers << dumb;
0348         return 0;
0349     } else if (request == DRM_IOCTL_MODE_DESTROY_DUMB) {
0350         auto args = static_cast<drm_mode_destroy_dumb*>(arg);
0351         auto it = std::find_if(gpu->dumbBuffers.begin(), gpu->dumbBuffers.end(), [args](const auto &buf){return buf->handle == args->handle;});
0352         if (it == gpu->dumbBuffers.end()) {
0353             qWarning("buffer %u not found!", args->handle);
0354             return -(errno = EINVAL);
0355         } else {
0356             gpu->dumbBuffers.erase(it);
0357             return 0;
0358         }
0359     } else if (request == DRM_IOCTL_MODE_MAP_DUMB) {
0360         auto args = static_cast<drm_mode_map_dumb*>(arg);
0361         auto it = std::find_if(gpu->dumbBuffers.begin(), gpu->dumbBuffers.end(), [args](const auto &buf){return buf->handle == args->handle;});
0362         if (it == gpu->dumbBuffers.end()) {
0363             qWarning("buffer %u not found!", args->handle);
0364             return -(errno = EINVAL);
0365         } else {
0366             args->offset = reinterpret_cast<uintptr_t>((*it)->data.data());
0367             return 0;
0368         }
0369     }
0370     return -(errno = ENOTSUP);
0371 }
0372 
0373 drmModeResPtr drmModeGetResources(int fd)
0374 {
0375     GPU(fd, nullptr)
0376     drmModeResPtr res = new drmModeRes;
0377 
0378     res->count_connectors = gpu->connectors.count();
0379     res->connectors = res->count_connectors ? new uint32_t[res->count_connectors] : nullptr;
0380     int i = 0;
0381     for (const auto &conn : std::as_const(gpu->connectors)) {
0382         res->connectors[i++] = conn->id;
0383     }
0384 
0385     res->count_encoders = gpu->encoders.count();
0386     res->encoders = res->count_encoders ? new uint32_t[res->count_encoders] : nullptr;
0387     i = 0;
0388     for (const auto &enc : std::as_const(gpu->encoders)) {
0389         res->encoders[i++] = enc->id;
0390     }
0391 
0392     res->count_crtcs = gpu->crtcs.count();
0393     res->crtcs = res->count_crtcs ? new uint32_t[res->count_crtcs] : nullptr;
0394     i = 0;
0395     for (const auto &crtc : std::as_const(gpu->crtcs)) {
0396         res->crtcs[i++] = crtc->id;
0397     }
0398 
0399     res->count_fbs = gpu->fbs.count();
0400     res->fbs = res->count_fbs ? new uint32_t[res->count_fbs] : nullptr;
0401     i = 0;
0402     for (const auto &fb : std::as_const(gpu->fbs)) {
0403         res->fbs[i++] = fb->id;
0404     }
0405 
0406     res->min_width = 0;
0407     res->min_height = 0;
0408     res->max_width = 2 << 14;
0409     res->max_height = 2 << 14;
0410 
0411     gpu->resPtrs << res;
0412     return res;
0413 }
0414 
0415 int drmModeAddFB(int fd, uint32_t width, uint32_t height, uint8_t depth,
0416                  uint8_t bpp, uint32_t pitch, uint32_t bo_handle,
0417                  uint32_t *buf_id)
0418 {
0419     GPU(fd, EINVAL)
0420     auto fb = new MockFb(gpu, width, height);
0421     *buf_id = fb->id;
0422     return 0;
0423 }
0424 
0425 int drmModeAddFB2(int fd, uint32_t width, uint32_t height,
0426                   uint32_t pixel_format, const uint32_t bo_handles[4],
0427                   const uint32_t pitches[4], const uint32_t offsets[4],
0428                   uint32_t *buf_id, uint32_t flags)
0429 {
0430     GPU(fd, EINVAL)
0431     auto fb = new MockFb(gpu, width, height);
0432     *buf_id = fb->id;
0433     return 0;
0434 }
0435 
0436 int drmModeAddFB2WithModifiers(int fd, uint32_t width, uint32_t height,
0437                                uint32_t pixel_format, const uint32_t bo_handles[4],
0438                                const uint32_t pitches[4], const uint32_t offsets[4],
0439                                const uint64_t modifier[4], uint32_t *buf_id,
0440                                uint32_t flags)
0441 {
0442     GPU(fd, EINVAL)
0443     if (!gpu->deviceCaps.contains(DRM_CAP_ADDFB2_MODIFIERS)) {
0444         return -(errno = ENOTSUP);
0445     }
0446     auto fb = new MockFb(gpu, width, height);
0447     *buf_id = fb->id;
0448     return 0;
0449 }
0450 
0451 int drmModeRmFB(int fd, uint32_t bufferId)
0452 {
0453     GPU(fd, EINVAL)
0454     auto it = std::find_if(gpu->fbs.begin(), gpu->fbs.end(), [bufferId](const auto &fb){return fb->id == bufferId;});
0455     if (it == gpu->fbs.end()) {
0456         qWarning("invalid bufferId %u passed to drmModeRmFB", bufferId);
0457         return EINVAL;
0458     } else {
0459         auto fb = *it;
0460         gpu->fbs.erase(it);
0461         for (const auto &plane : std::as_const(gpu->planes)) {
0462             if (plane->nextFb == fb) {
0463                 plane->nextFb = nullptr;
0464             }
0465             if (plane->currentFb == fb) {
0466                 qWarning("current fb %u of plane %u got removed. Deactivating plane", bufferId, plane->id);
0467                 plane->setProp(QStringLiteral("CRTC_ID"), 0);
0468                 plane->setProp(QStringLiteral("FB_ID"), 0);
0469                 plane->currentFb = nullptr;
0470 
0471                 auto crtc = gpu->findCrtc(plane->getProp(QStringLiteral("CRTC_ID")));
0472                 Q_ASSERT(crtc);
0473                 crtc->setProp(QStringLiteral("ACTIVE"), 0);
0474                 qWarning("deactvating crtc %u", crtc->id);
0475 
0476                 for (const auto &conn : std::as_const(gpu->connectors)) {
0477                     if (conn->getProp(QStringLiteral("CRTC_ID")) == crtc->id) {
0478                         conn->setProp(QStringLiteral("CRTC_ID"), 0);
0479                         qWarning("deactvating connector %u", conn->id);
0480                     }
0481                 }
0482             }
0483         }
0484         delete fb;
0485         return 0;
0486     }
0487 }
0488 
0489 drmModeCrtcPtr drmModeGetCrtc(int fd, uint32_t crtcId)
0490 {
0491     GPU(fd, nullptr);
0492     if (auto crtc = gpu->findCrtc(crtcId)) {
0493         drmModeCrtcPtr c = new drmModeCrtc;
0494         c->crtc_id = crtcId;
0495         c->buffer_id = crtc->currentFb ? crtc->currentFb->id : 0;
0496         c->gamma_size = crtc->gamma_size;
0497         c->mode_valid = crtc->modeValid;
0498         c->mode = crtc->mode;
0499         c->x = 0;
0500         c->y = 0;
0501         c->width = crtc->mode.hdisplay;
0502         c->height = crtc->mode.vdisplay;
0503         gpu->drmCrtcs << c;
0504         return c;
0505     } else {
0506         qWarning("invalid crtcId %u passed to drmModeGetCrtc", crtcId);
0507         errno = EINVAL;
0508         return nullptr;
0509     }
0510 }
0511 
0512 int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
0513                    uint32_t x, uint32_t y, uint32_t *connectors, int count,
0514                    drmModeModeInfoPtr mode)
0515 {
0516     GPU(fd, -EINVAL);
0517     auto crtc = gpu->findCrtc(crtcId);
0518     if (!crtc) {
0519         qWarning("invalid crtcId %u passed to drmModeSetCrtc", crtcId);
0520         return -(errno = EINVAL);
0521     }
0522     auto oldModeBlob = crtc->getProp(QStringLiteral("MODE_ID"));
0523     uint32_t modeBlob = 0;
0524     if (mode) {
0525         drmModeCreatePropertyBlob(fd, mode, sizeof(drmModeModeInfo), &modeBlob);
0526     }
0527 
0528     auto req = drmModeAtomicAlloc();
0529     req->legacyEmulation = true;
0530     drmModeAtomicAddProperty(req, crtcId, crtc->getPropId(QStringLiteral("MODE_ID")), modeBlob);
0531     drmModeAtomicAddProperty(req, crtcId, crtc->getPropId(QStringLiteral("ACTIVE")), modeBlob && count);
0532     QVector<uint32_t> conns;
0533     for (int i = 0; i < count; i++) {
0534         conns << connectors[i];
0535     }
0536     for (const auto &conn : std::as_const(gpu->connectors)) {
0537         if (conns.contains(conn->id)) {
0538             drmModeAtomicAddProperty(req, conn->id, conn->getPropId(QStringLiteral("CRTC_ID")), modeBlob ? crtc->id : 0);
0539             conns.removeOne(conn->id);
0540         } else if (conn->getProp(QStringLiteral("CRTC_ID")) == crtc->id) {
0541             drmModeAtomicAddProperty(req, conn->id, conn->getPropId(QStringLiteral("CRTC_ID")), 0);
0542         }
0543     }
0544     if (!conns.isEmpty()) {
0545         for (const auto &c : std::as_const(conns)) {
0546             qWarning("invalid connector %u passed to drmModeSetCrtc", c);
0547         }
0548         drmModeAtomicFree(req);
0549         return -(errno = EINVAL);
0550     }
0551     drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_ID")), modeBlob && count ? crtc->id : 0);
0552     drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_X")), x);
0553     drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_Y")), y);
0554     drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_W")), mode->hdisplay - x);
0555     drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_H")), mode->vdisplay - y);
0556     drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("FB_ID")), bufferId);
0557     int result = drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr);
0558     drmModeAtomicFree(req);
0559     if (result == 0) {
0560         drmModeDestroyPropertyBlob(fd, oldModeBlob);
0561     }
0562     return result;
0563 }
0564 
0565 int drmModeSetCursor(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height)
0566 {
0567     GPU(fd, -EINVAL);
0568     if (auto crtc = gpu->findCrtc(crtcId)) {
0569         if (bo_handle != 0) {
0570             auto it = std::find_if(gpu->dumbBuffers.constBegin(), gpu->dumbBuffers.constEnd(), [bo_handle](const auto &bo){return bo->handle == bo_handle;});
0571             if (it == gpu->dumbBuffers.constEnd()) {
0572                 qWarning("invalid bo_handle %u passed to drmModeSetCursor", bo_handle);
0573                 return -(errno = EINVAL);
0574             }
0575             crtc->cursorBo = (*it).get();
0576         } else {
0577             crtc->cursorBo = nullptr;
0578         }
0579         crtc->cursorRect.setSize(QSize(width, height));
0580         return 0;
0581     } else {
0582         qWarning("invalid crtcId %u passed to drmModeSetCursor", crtcId);
0583         return -(errno = EINVAL);
0584     }
0585 }
0586 
0587 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)
0588 {
0589     GPU(fd, -EINVAL);
0590     return -(errno = ENOTSUP);
0591 }
0592 
0593 int drmModeMoveCursor(int fd, uint32_t crtcId, int x, int y)
0594 {
0595     GPU(fd, -EINVAL);
0596     if (auto crtc = gpu->findCrtc(crtcId)) {
0597         crtc->cursorRect.moveTo(x, y);
0598         return 0;
0599     } else {
0600         qWarning("invalid crtcId %u passed to drmModeMoveCursor", crtcId);
0601         return -(errno = EINVAL);
0602     }
0603 }
0604 
0605 drmModeEncoderPtr drmModeGetEncoder(int fd, uint32_t encoder_id)
0606 {
0607     GPU(fd, nullptr);
0608     auto it = std::find_if(gpu->encoders.constBegin(), gpu->encoders.constEnd(), [encoder_id](const auto &e){return e->id == encoder_id;});
0609     if (it == gpu->encoders.constEnd()) {
0610         qWarning("invalid encoder_id %u passed to drmModeGetEncoder", encoder_id);
0611         errno = EINVAL;
0612         return nullptr;
0613     } else {
0614         auto encoder = *it;
0615         drmModeEncoderPtr enc = new drmModeEncoder;
0616         enc->encoder_id = encoder_id;
0617         enc->crtc_id = encoder->crtc ? encoder->crtc->id : 0;
0618         enc->encoder_type = 0;
0619         enc->possible_crtcs = encoder->possible_crtcs;
0620         enc->possible_clones = encoder->possible_clones;
0621 
0622         gpu->drmEncoders << enc;
0623         return enc;
0624     }
0625 }
0626 
0627 drmModeConnectorPtr drmModeGetConnector(int fd, uint32_t connectorId)
0628 {
0629     GPU(fd, nullptr);
0630     if (auto conn = gpu->findConnector(connectorId)) {
0631         drmModeConnectorPtr c = new drmModeConnector{};
0632         c->connector_id = conn->id;
0633         c->connection = conn->connection;
0634         c->connector_type = conn->type;
0635         c->encoder_id = conn->encoder ? conn->encoder->id : 0;
0636         c->count_encoders = conn->encoder ? 1 : 0;
0637         c->encoders = c->count_encoders ? new uint32_t[1] : nullptr;
0638         if (c->encoders) {
0639             c->encoders[0] = conn->encoder->id;
0640         }
0641         c->count_modes = conn->modes.count();
0642         c->modes = c->count_modes ? new drmModeModeInfo[c->count_modes] : nullptr;
0643         for (int i = 0; i < c->count_modes; i++) {
0644             c->modes[i] = conn->modes[i];
0645         }
0646         c->mmHeight = 900;
0647         c->mmWidth = 1600;
0648         c->subpixel = DRM_MODE_SUBPIXEL_HORIZONTAL_RGB;
0649 
0650         c->connector_type_id = DRM_MODE_CONNECTOR_DisplayPort;// ?
0651 
0652         // these are not used nor will they be
0653         c->count_props = -1;
0654         c->props = nullptr;
0655         c->prop_values = nullptr;
0656 
0657         gpu->drmConnectors << c;
0658         return c;
0659     } else {
0660         qWarning("invalid connectorId %u passed to drmModeGetConnector", connectorId);
0661         errno = EINVAL;
0662         return nullptr;
0663     }
0664 }
0665 
0666 drmModeConnectorPtr drmModeGetConnectorCurrent(int fd, uint32_t connector_id)
0667 {
0668     return drmModeGetConnector(fd, connector_id);
0669 }
0670 
0671 int drmModeCrtcSetGamma(int fd, uint32_t crtc_id, uint32_t size, uint16_t *red, uint16_t *green, uint16_t *blue)
0672 {
0673     return -(errno = ENOTSUP);
0674 }
0675 
0676 int drmModePageFlip(int fd, uint32_t crtc_id, uint32_t fb_id, uint32_t flags, void *user_data)
0677 {
0678     GPU(fd, -EINVAL);
0679     auto crtc = gpu->findCrtc(crtc_id);
0680     if (!crtc) {
0681         qWarning("invalid crtc_id %u passed to drmModePageFlip", crtc_id);
0682         return -(errno = EINVAL);
0683     }
0684     auto req = drmModeAtomicAlloc();
0685     req->legacyEmulation = true;
0686     drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("FB_ID")), fb_id);
0687     int result = drmModeAtomicCommit(fd, req, flags, user_data);
0688     drmModeAtomicFree(req);
0689     return result;
0690 }
0691 
0692 
0693 drmModePlaneResPtr drmModeGetPlaneResources(int fd)
0694 {
0695     GPU(fd, nullptr);
0696     drmModePlaneResPtr res = new drmModePlaneRes;
0697     res->count_planes = gpu->planes.count();
0698     res->planes = res->count_planes ? new uint32_t[res->count_planes] : nullptr;
0699     for (uint i = 0; i < res->count_planes; i++) {
0700         res->planes[i] = gpu->planes[i]->id;
0701     }
0702     gpu->drmPlaneRes << res;
0703     return res;
0704 }
0705 
0706 drmModePlanePtr drmModeGetPlane(int fd, uint32_t plane_id)
0707 {
0708     GPU(fd, nullptr);
0709     if (auto plane = gpu->findPlane(plane_id)) {
0710         drmModePlanePtr p = new drmModePlane;
0711         p->plane_id = plane_id;
0712         p->crtc_id = plane->getProp(QStringLiteral("CRTC_ID"));
0713         p->crtc_x = plane->getProp(QStringLiteral("CRTC_X"));
0714         p->crtc_y = plane->getProp(QStringLiteral("CRTC_Y"));
0715         p->fb_id = plane->getProp(QStringLiteral("FB_ID"));
0716         p->x = plane->getProp(QStringLiteral("SRC_X"));
0717         p->y = plane->getProp(QStringLiteral("SRC_Y"));
0718         p->possible_crtcs = plane->possibleCrtcs;
0719 
0720         // unused atm:
0721         p->count_formats = 0;
0722         p->formats = nullptr;
0723         p->gamma_size = 0;
0724 
0725         gpu->drmPlanes << p;
0726         return p;
0727     } else {
0728         qWarning("invalid plane_id %u passed to drmModeGetPlane", plane_id);
0729         errno = EINVAL;
0730         return nullptr;
0731     }
0732 }
0733 
0734 drmModePropertyPtr drmModeGetProperty(int fd, uint32_t propertyId)
0735 {
0736     GPU(fd, nullptr);
0737     for (const auto &obj : std::as_const(gpu->objects)) {
0738         for (auto &prop : std::as_const(obj->props)) {
0739             if (prop.id == propertyId) {
0740                 drmModePropertyPtr p = new drmModePropertyRes;
0741                 p->prop_id = prop.id;
0742                 p->flags = prop.flags;
0743                 auto arr = prop.name.toLocal8Bit();
0744                 strcpy(p->name, arr.constData());
0745 
0746                 p->count_blobs = prop.flags & DRM_MODE_PROP_BLOB ? 1 : 0;
0747                 if (p->count_blobs) {
0748                     p->blob_ids = new uint32_t[1];
0749                     p->blob_ids[0] = prop.value;
0750                 } else {
0751                     p->blob_ids = nullptr;
0752                 }
0753 
0754                 p->count_enums = prop.enums.count();
0755                 p->enums = new drm_mode_property_enum[p->count_enums];
0756                 for (int i = 0; i < p->count_enums; i++) {
0757                     strcpy(p->enums[i].name, prop.enums[i].constData());
0758                     p->enums[i].value = i;
0759                 }
0760 
0761                 p->count_values = 1;
0762                 p->values = new uint64_t[1];
0763                 p->values[0] = prop.value;
0764 
0765                 gpu->drmProps << p;
0766                 return p;
0767             }
0768         }
0769     }
0770     qWarning("invalid propertyId %u passed to drmModeGetProperty", propertyId);
0771     errno = EINVAL;
0772     return nullptr;
0773 }
0774 
0775 void drmModeFreeProperty(drmModePropertyPtr ptr)
0776 {
0777     if (!ptr) {
0778         return;
0779     }
0780     for (const auto &gpu : std::as_const(s_gpus)) {
0781         if (gpu->drmProps.removeOne(ptr)) {
0782             delete[] ptr->values;
0783             delete[] ptr->blob_ids;
0784             delete[] ptr->enums;
0785             delete ptr;
0786             return;
0787         }
0788     }
0789 }
0790 
0791 
0792 
0793 drmModePropertyBlobPtr drmModeGetPropertyBlob(int fd, uint32_t blob_id)
0794 {
0795     GPU(fd, nullptr);
0796     if (blob_id == 0) {
0797         return nullptr;
0798     }
0799     auto it = std::find_if(gpu->propertyBlobs.begin(), gpu->propertyBlobs.end(), [blob_id](const auto &blob) {
0800         return blob->id == blob_id;
0801     });
0802     if (it == gpu->propertyBlobs.end()) {
0803         qWarning("invalid blob_id %u passed to drmModeGetPropertyBlob", blob_id);
0804         errno = EINVAL;
0805         return nullptr;
0806     } else {
0807         auto blob = new drmModePropertyBlobRes;
0808         blob->id = (*it)->id;
0809         blob->length = (*it)->size;
0810         blob->data = malloc(blob->length);
0811         memcpy(blob->data, (*it)->data, blob->length);
0812         return blob;
0813     }
0814 }
0815 
0816 void drmModeFreePropertyBlob(drmModePropertyBlobPtr ptr)
0817 {
0818     if (!ptr) {
0819         return;
0820     }
0821     for (const auto &gpu : std::as_const(s_gpus)) {
0822         if (gpu->drmPropertyBlobs.removeOne(ptr)) {
0823             free(ptr->data);
0824             delete ptr;
0825             return;
0826         }
0827     }
0828 }
0829 
0830 int drmModeConnectorSetProperty(int fd, uint32_t connector_id, uint32_t property_id, uint64_t value)
0831 {
0832     return drmModeObjectSetProperty(fd, connector_id, DRM_MODE_OBJECT_CONNECTOR, property_id, value);
0833 }
0834 
0835 static uint32_t getType(MockObject *obj)
0836 {
0837     if (dynamic_cast<MockConnector*>(obj)) {
0838         return DRM_MODE_OBJECT_CONNECTOR;
0839     } else if (dynamic_cast<MockCrtc*>(obj)) {
0840         return DRM_MODE_OBJECT_CRTC;
0841     } else if (dynamic_cast<MockPlane*>(obj)) {
0842         return DRM_MODE_OBJECT_PLANE;
0843     } else {
0844         return DRM_MODE_OBJECT_ANY;
0845     }
0846 }
0847 
0848 drmModeObjectPropertiesPtr drmModeObjectGetProperties(int fd, uint32_t object_id, uint32_t object_type)
0849 {
0850     GPU(fd, nullptr);
0851     auto it = std::find_if(gpu->objects.constBegin(), gpu->objects.constEnd(), [object_id](const auto &obj){return obj->id == object_id;});
0852     if (it == gpu->objects.constEnd()) {
0853         qWarning("invalid object_id %u passed to drmModeObjectGetProperties", object_id);
0854         errno = EINVAL;
0855         return nullptr;
0856     } else {
0857         auto obj = *it;
0858         if (auto type = getType(obj); type != object_type) {
0859             qWarning("wrong object_type %u passed to drmModeObjectGetProperties for object %u with type %u", object_type, object_id, type);
0860             errno = EINVAL;
0861             return nullptr;
0862         }
0863         QVector<MockProperty> props;
0864         bool deviceAtomic = gpu->clientCaps.contains(DRM_CLIENT_CAP_ATOMIC) && gpu->clientCaps[DRM_CLIENT_CAP_ATOMIC];
0865         for (const auto &prop : std::as_const(obj->props)) {
0866             if (deviceAtomic || !(prop.flags & DRM_MODE_PROP_ATOMIC)) {
0867                 props << prop;
0868             }
0869         }
0870         drmModeObjectPropertiesPtr p = new drmModeObjectProperties;
0871         p->count_props = props.count();
0872         p->props = new uint32_t[p->count_props];
0873         p->prop_values = new uint64_t[p->count_props];
0874         int i = 0;
0875         for (const auto &prop : std::as_const(props)) {
0876             p->props[i] = prop.id;
0877             p->prop_values[i] = prop.value;
0878             i++;
0879         }
0880         gpu->drmObjectProperties << p;
0881         return p;
0882     }
0883 }
0884 
0885 void drmModeFreeObjectProperties(drmModeObjectPropertiesPtr ptr)
0886 {
0887     for (const auto &gpu : std::as_const(s_gpus)) {
0888         if (gpu->drmObjectProperties.removeOne(ptr)) {
0889             delete[] ptr->props;
0890             delete[] ptr->prop_values;
0891             delete ptr;
0892             return;
0893         }
0894     }
0895 }
0896 
0897 int drmModeObjectSetProperty(int fd, uint32_t object_id, uint32_t object_type, uint32_t property_id, uint64_t value)
0898 {
0899     GPU(fd, -EINVAL);
0900     auto it = std::find_if(gpu->objects.constBegin(), gpu->objects.constEnd(), [object_id](const auto &obj){return obj->id == object_id;});
0901     if (it == gpu->objects.constEnd()) {
0902         qWarning("invalid object_id %u passed to drmModeObjectSetProperty", object_id);
0903         return -(errno = EINVAL);
0904     } else {
0905         auto obj = *it;
0906         if (auto type = getType(obj); type != object_type) {
0907             qWarning("wrong object_type %u passed to drmModeObjectSetProperty for object %u with type %u", object_type, object_id, type);
0908             return -(errno = EINVAL);
0909         }
0910         auto req = drmModeAtomicAlloc();
0911         req->legacyEmulation = true;
0912         drmModeAtomicAddProperty(req, object_id, property_id, value);
0913         int result = drmModeAtomicCommit(fd, req, 0, nullptr);
0914         drmModeAtomicFree(req);
0915         return result;
0916     }
0917 }
0918 
0919 static QVector<drmModeAtomicReqPtr> s_atomicReqs;
0920 
0921 drmModeAtomicReqPtr drmModeAtomicAlloc(void)
0922 {
0923     auto req = new drmModeAtomicReq;
0924     s_atomicReqs << req;
0925     return req;
0926 }
0927 
0928 void drmModeAtomicFree(drmModeAtomicReqPtr req)
0929 {
0930     s_atomicReqs.removeOne(req);
0931     delete req;
0932 }
0933 
0934 int drmModeAtomicAddProperty(drmModeAtomicReqPtr req, uint32_t object_id, uint32_t property_id, uint64_t value)
0935 {
0936     if (!req) {
0937         return -(errno = EINVAL);
0938     }
0939     Prop p;
0940     p.obj = object_id;
0941     p.prop = property_id;
0942     p.value = value;
0943     req->props << p;
0944     return req->props.count();
0945 }
0946 
0947 static bool checkIfEqual(const drmModeModeInfo &one, const drmModeModeInfo &two)
0948 {
0949     return one.clock       == two.clock
0950         && one.hdisplay    == two.hdisplay
0951         && one.hsync_start == two.hsync_start
0952         && one.hsync_end   == two.hsync_end
0953         && one.htotal      == two.htotal
0954         && one.hskew       == two.hskew
0955         && one.vdisplay    == two.vdisplay
0956         && one.vsync_start == two.vsync_start
0957         && one.vsync_end   == two.vsync_end
0958         && one.vtotal      == two.vtotal
0959         && one.vscan       == two.vscan
0960         && one.vrefresh    == two.vrefresh;
0961 }
0962 
0963 int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req, uint32_t flags, void *user_data)
0964 {
0965     GPU(fd, -EINVAL);
0966     if (!req->legacyEmulation && (!gpu->clientCaps.contains(DRM_CLIENT_CAP_ATOMIC) || !gpu->clientCaps[DRM_CLIENT_CAP_ATOMIC])) {
0967         qWarning("drmModeAtomicCommit requires the atomic capability");
0968         return -(errno = EINVAL);
0969     }
0970 
0971     // verify flags
0972     if ((flags & DRM_MODE_ATOMIC_NONBLOCK) && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) {
0973         qWarning() << "NONBLOCK and MODESET are not allowed together";
0974         return -(errno = EINVAL);
0975     } else if ((flags & DRM_MODE_ATOMIC_TEST_ONLY) && (flags & DRM_MODE_PAGE_FLIP_EVENT)) {
0976         qWarning() << "TEST_ONLY and PAGE_FLIP_EVENT are not allowed together";
0977         return -(errno = EINVAL);
0978     } else if (flags & DRM_MODE_PAGE_FLIP_ASYNC) {
0979         qWarning() << "PAGE_FLIP_ASYNC is currently not supported with AMS";
0980         return -(errno = EINVAL);
0981     }
0982 
0983     QVector<MockConnector> connCopies;
0984     for (const auto &conn : std::as_const(gpu->connectors)) {
0985         connCopies << *conn;
0986     }
0987     QVector<MockCrtc> crtcCopies;
0988     for (const auto &crtc : std::as_const(gpu->crtcs)) {
0989         crtcCopies << *crtc;
0990     }
0991     QVector<MockPlane> planeCopies;
0992     for (const auto &plane : std::as_const(gpu->planes)) {
0993         planeCopies << *plane;
0994     }
0995 
0996     QVector<MockObject*> objects;
0997     for (int i = 0; i < connCopies.count(); i++) {
0998         objects << &connCopies[i];
0999     }
1000     for (int i = 0; i < crtcCopies.count(); i++) {
1001         objects << &crtcCopies[i];
1002     }
1003     for (int i = 0; i < planeCopies.count(); i++) {
1004         objects << &planeCopies[i];
1005     }
1006 
1007     // apply changes to the copies
1008     for (int i = 0; i < req->props.count(); i++) {
1009         auto p = req->props[i];
1010         auto it = std::find_if(objects.constBegin(), objects.constEnd(), [p](const auto &obj){return obj->id == p.obj;});
1011         if (it == objects.constEnd()) {
1012             qWarning("Object %u in atomic request not found!", p.obj);
1013             return -(errno = EINVAL);
1014         }
1015         auto &obj = *it;
1016         if (obj->id == p.obj) {
1017             auto prop = std::find_if(obj->props.begin(), obj->props.end(), [p](const auto &prop){return prop.id == p.prop;});
1018             if (prop == obj->props.end()) {
1019                 qWarning("Property %u in atomic request for object %u not found!", p.prop, p.obj);
1020                 return -(errno = EINVAL);
1021             }
1022             if (prop->value != p.value) {
1023                 if (!(flags & DRM_MODE_ATOMIC_ALLOW_MODESET) && (prop->name == QStringLiteral("CRTC_ID") || prop->name == QStringLiteral("ACTIVE"))) {
1024                     qWarning("Atomic request without DRM_MODE_ATOMIC_ALLOW_MODESET tries to do a modeset with object %u", obj->id);
1025                     return -(errno = EINVAL);
1026                 }
1027                 if (prop->flags & DRM_MODE_PROP_BLOB) {
1028                     auto blobExists = gpu->getBlob(p.value) != nullptr;
1029                     if (blobExists != (p.value > 0)) {
1030                         qWarning("Atomic request tries to set property %s on obj %u to invalid blob id %lu", qPrintable(prop->name), obj->id, p.value);
1031                         return -(errno = EINVAL);
1032                     }
1033                 }
1034                 prop->value = p.value;
1035             }
1036         }
1037     }
1038 
1039     // check if the desired changes are allowed
1040     struct Pipeline {
1041         MockCrtc *crtc;
1042         QVector<MockConnector*> conns;
1043         MockPlane *primaryPlane = nullptr;
1044     };
1045     QVector<Pipeline> pipelines;
1046     for (int i = 0; i < crtcCopies.count(); i++) {
1047         if (crtcCopies[i].getProp(QStringLiteral("ACTIVE"))) {
1048             auto blob = gpu->getBlob(crtcCopies[i].getProp(QStringLiteral("MODE_ID")));
1049             if (!blob) {
1050                 qWarning("Atomic request tries to enable CRTC %u without a mode", crtcCopies[i].id);
1051                 return -(errno = EINVAL);
1052             } else if (blob->size != sizeof(drmModeModeInfo)) {
1053                 qWarning("Atomic request tries to enable CRTC %u with an invalid mode blob", crtcCopies[i].id);
1054                 return -(errno = EINVAL);
1055             }
1056             Pipeline pipeline;
1057             pipeline.crtc = &crtcCopies[i];
1058             pipelines << pipeline;
1059         }
1060     }
1061     for (int i = 0; i < connCopies.count(); i++) {
1062         if (auto crtc = connCopies[i].getProp(QStringLiteral("CRTC_ID"))) {
1063             bool found = false;
1064             for (int p = 0; p < pipelines.count(); p++) {
1065                 if (pipelines[p].crtc->id == crtc) {
1066                     pipelines[p].conns << &connCopies[i];
1067                     found = true;
1068                     break;
1069                 }
1070             }
1071             if (!found) {
1072                 qWarning("CRTC_ID of connector %u points to inactive or wrong crtc", connCopies[i].id);
1073                 return -(errno = EINVAL);
1074             }
1075         }
1076     }
1077     for (int i = 0; i < planeCopies.count(); i++) {
1078         if (auto crtc = planeCopies[i].getProp(QStringLiteral("CRTC_ID"))) {
1079             bool found = false;
1080             for (int p = 0; p < pipelines.count(); p++) {
1081                 if (pipelines[p].crtc->id == crtc) {
1082                     if (pipelines[p].primaryPlane) {
1083                         qWarning("crtc %u has more than one primary planes assigned: %u and %u", pipelines[p].crtc->id, pipelines[p].primaryPlane->id, planeCopies[i].id);
1084                         return -(errno = EINVAL);
1085                     } else if (!(planeCopies[i].possibleCrtcs & (1 << pipelines[p].crtc->pipeIndex))) {
1086                         qWarning("crtc %u is not suitable for primary plane %u", pipelines[p].crtc->id, planeCopies[i].id);
1087                         return -(errno = EINVAL);
1088                     } else {
1089                         pipelines[p].primaryPlane = &planeCopies[i];
1090                         found = true;
1091                         break;
1092                     }
1093                 }
1094             }
1095             if (!found) {
1096                 qWarning("CRTC_ID of plane %u points to inactive or wrong crtc", planeCopies[i].id);
1097                 return -(errno = EINVAL);
1098             }
1099             auto fbId = planeCopies[i].getProp(QStringLiteral("FB_ID"));
1100             if (!fbId) {
1101                 qWarning("FB_ID of active plane %u is 0", planeCopies[i].id);
1102                 return -(errno = EINVAL);
1103             }
1104             auto it = std::find_if(gpu->fbs.constBegin(), gpu->fbs.constEnd(), [fbId](auto fb){return fb->id == fbId;});
1105             if (it == gpu->fbs.constEnd()) {
1106                 qWarning("FB_ID %lu of active plane %u is invalid", fbId, planeCopies[i].id);
1107                 return -(errno = EINVAL);
1108             }
1109             planeCopies[i].nextFb = *it;
1110         } else {
1111             planeCopies[i].nextFb = nullptr;
1112         }
1113     }
1114     for (const auto &p : std::as_const(pipelines)) {
1115         if (p.conns.isEmpty()) {
1116             qWarning("Active crtc %u has no assigned connectors", p.crtc->id);
1117             return -(errno = EINVAL);
1118         } else if (!p.primaryPlane) {
1119             qWarning("Active crtc %u has no assigned primary plane", p.crtc->id);
1120             return -(errno = EINVAL);
1121         } else {
1122             drmModeModeInfo mode = *static_cast<drmModeModeInfo*>(gpu->getBlob(p.crtc->getProp(QStringLiteral("MODE_ID")))->data);
1123             for (const auto &conn : p.conns) {
1124                 bool modeFound = std::find_if(conn->modes.constBegin(), conn->modes.constEnd(), [mode](const auto &m){
1125                     return checkIfEqual(mode, m);
1126                 }) != conn->modes.constEnd();
1127                 if (!modeFound) {
1128                     qWarning("mode on crtc %u is incompatible with connector %u", p.crtc->id, conn->id);
1129                     return -(errno = EINVAL);
1130                 }
1131             }
1132         }
1133     }
1134 
1135     // if wanted, apply them
1136 
1137     if (!(flags & DRM_MODE_ATOMIC_TEST_ONLY)) {
1138         for (auto &conn : std::as_const(gpu->connectors)) {
1139             auto it = std::find_if(connCopies.constBegin(), connCopies.constEnd(), [conn](auto c){return c.id == conn->id;});
1140             if (it == connCopies.constEnd()) {
1141                 qCritical("implementation error: can't find connector %u", conn->id);
1142                 return -(errno = EINVAL);
1143             }
1144             *conn = *it;
1145         }
1146         for (auto &crtc : std::as_const(gpu->crtcs)) {
1147             auto it = std::find_if(crtcCopies.constBegin(), crtcCopies.constEnd(), [crtc](auto c){return c.id == crtc->id;});
1148             if (it == crtcCopies.constEnd()) {
1149                 qCritical("implementation error: can't find crtc %u", crtc->id);
1150                 return -(errno = EINVAL);
1151             }
1152             *crtc = *it;
1153         }
1154         for (auto &plane : std::as_const(gpu->planes)) {
1155             auto it = std::find_if(planeCopies.constBegin(), planeCopies.constEnd(), [plane](auto c){return c.id == plane->id;});
1156             if (it == planeCopies.constEnd()) {
1157                 qCritical("implementation error: can't find plane %u", plane->id);
1158                 return -(errno = EINVAL);
1159             }
1160             *plane = *it;
1161         }
1162 
1163         if (flags & DRM_MODE_PAGE_FLIP_EVENT) {
1164             // Unsupported
1165         }
1166     }
1167 
1168     return 0;
1169 }
1170 
1171 
1172 int drmModeCreatePropertyBlob(int fd, const void *data, size_t size, uint32_t *id)
1173 {
1174     GPU(fd, -EINVAL);
1175     if (!data || !size || !id) {
1176         return -(errno = EINVAL);
1177     }
1178     auto blob = std::make_unique<MockPropertyBlob>(gpu, data, size);
1179     *id = blob->id;
1180     gpu->propertyBlobs.push_back(std::move(blob));
1181     return 0;
1182 }
1183 
1184 int drmModeDestroyPropertyBlob(int fd, uint32_t id)
1185 {
1186     GPU(fd, -EINVAL);
1187     auto it = std::remove_if(gpu->propertyBlobs.begin(), gpu->propertyBlobs.end(), [id](const auto &blob) {
1188         return blob->id == id;
1189     });
1190     if (it == gpu->propertyBlobs.end()) {
1191         return -(errno = EINVAL);
1192     } else {
1193         gpu->propertyBlobs.erase(it, gpu->propertyBlobs.end());
1194         return 0;
1195     }
1196 }
1197 
1198 int drmModeCreateLease(int fd, const uint32_t *objects, int num_objects, int flags, uint32_t *lessee_id)
1199 {
1200     return -(errno = ENOTSUP);
1201 }
1202 
1203 drmModeLesseeListPtr drmModeListLessees(int fd)
1204 {
1205     return nullptr;
1206 }
1207 
1208 drmModeObjectListPtr drmModeGetLease(int fd)
1209 {
1210     return nullptr;
1211 }
1212 
1213 int drmModeRevokeLease(int fd, uint32_t lessee_id)
1214 {
1215     return -(errno = ENOTSUP);
1216 }
1217 
1218 void drmModeFreeResources(drmModeResPtr ptr)
1219 {
1220     for (const auto &gpu : std::as_const(s_gpus)) {
1221         if (gpu->resPtrs.removeOne(ptr)) {
1222             delete[] ptr->connectors;
1223             delete[] ptr->crtcs;
1224             delete[] ptr->encoders;
1225             delete[] ptr->fbs;
1226             delete ptr;
1227         }
1228     }
1229 }
1230 
1231 void drmModeFreePlaneResources(drmModePlaneResPtr ptr)
1232 {
1233     for (const auto &gpu : std::as_const(s_gpus)) {
1234         if (gpu->drmPlaneRes.removeOne(ptr)) {
1235             delete[] ptr->planes;
1236             delete ptr;
1237         }
1238     }
1239 }
1240 
1241 void drmModeFreeCrtc(drmModeCrtcPtr ptr)
1242 {
1243     for (const auto &gpu : std::as_const(s_gpus)) {
1244         if (gpu->drmCrtcs.removeOne(ptr)) {
1245             delete ptr;
1246             return;
1247         }
1248     }
1249     Q_UNREACHABLE();
1250 }
1251 
1252 void drmModeFreeConnector(drmModeConnectorPtr ptr)
1253 {
1254     for (const auto &gpu : std::as_const(s_gpus)) {
1255         if (gpu->drmConnectors.removeOne(ptr)) {
1256             delete[] ptr->encoders;
1257             delete[] ptr->props;
1258             delete[] ptr->prop_values;
1259             delete ptr;
1260             return;
1261         }
1262     }
1263     Q_UNREACHABLE();
1264 }
1265 
1266 void drmModeFreeEncoder(drmModeEncoderPtr ptr)
1267 {
1268     for (const auto &gpu : std::as_const(s_gpus)) {
1269         if (gpu->drmEncoders.removeOne(ptr)) {
1270             delete ptr;
1271             return;
1272         }
1273     }
1274     Q_UNREACHABLE();
1275 }
1276 
1277 void drmModeFreePlane(drmModePlanePtr ptr)
1278 {
1279     for (const auto &gpu : std::as_const(s_gpus)) {
1280         if (gpu->drmPlanes.removeOne(ptr)) {
1281             delete ptr;
1282             return;
1283         }
1284     }
1285     Q_UNREACHABLE();
1286 }