File indexing completed on 2024-05-12 05:30:27

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