/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2022 Xaver Hugl SPDX-License-Identifier: GPL-2.0-or-later */ #include "mock_drm.h" #include extern "C" { #include } #include #include #include #include // Mock impls static QMap s_gpus; static MockGpu *getGpu(int fd) { return s_gpus[fd]; } MockGpu::MockGpu(int fd, const QString &devNode, int numCrtcs, int gammaSize) : fd(fd) , devNode(devNode) { s_gpus.insert(fd, this); for (int i = 0; i < numCrtcs; i++) { const auto &plane = std::make_shared(this, PlaneType::Primary, i); crtcs << std::make_shared(this, plane, i, gammaSize); planes << plane; } deviceCaps.insert(MOCKDRM_DEVICE_CAP_ATOMIC, 1); deviceCaps.insert(DRM_CAP_DUMB_BUFFER, 1); deviceCaps.insert(DRM_CAP_PRIME, DRM_PRIME_CAP_IMPORT | DRM_PRIME_CAP_EXPORT); deviceCaps.insert(DRM_CAP_ASYNC_PAGE_FLIP, 0); deviceCaps.insert(DRM_CAP_ADDFB2_MODIFIERS, 1); } MockGpu::~MockGpu() { s_gpus.remove(fd); } MockPropertyBlob *MockGpu::getBlob(uint32_t id) const { auto it = std::find_if(propertyBlobs.begin(), propertyBlobs.end(), [id](const auto &propBlob) { return propBlob->id == id; }); return it == propertyBlobs.end() ? nullptr : it->get(); } MockConnector *MockGpu::findConnector(uint32_t id) const { auto it = std::find_if(connectors.constBegin(), connectors.constEnd(), [id](const auto &c){return c->id == id;}); return it == connectors.constEnd() ? nullptr : (*it).get(); } MockCrtc *MockGpu::findCrtc(uint32_t id) const { auto it = std::find_if(crtcs.constBegin(), crtcs.constEnd(), [id](const auto &c){return c->id == id;}); return it == crtcs.constEnd() ? nullptr : (*it).get(); } MockPlane *MockGpu::findPlane(uint32_t id) const { auto it = std::find_if(planes.constBegin(), planes.constEnd(), [id](const auto &p){return p->id == id;}); return it == planes.constEnd() ? nullptr : (*it).get(); } void MockGpu::flipPage(uint32_t crtcId) { auto crtc = findCrtc(crtcId); Q_ASSERT(crtc); for (const auto &plane : std::as_const(planes)) { if (plane->getProp(QStringLiteral("CRTC_ID")) == crtc->id) { plane->currentFb = plane->nextFb; } } // TODO page flip event? } // MockObject::MockObject(MockGpu *gpu) : id(gpu->idCounter++) , gpu(gpu) { gpu->objects << this; } MockObject::~MockObject() { gpu->objects.removeOne(this); } uint64_t MockObject::getProp(const QString &propName) const { for (const auto &prop : std::as_const(props)) { if (prop.name == propName) { return prop.value; } } Q_UNREACHABLE(); } void MockObject::setProp(const QString &propName, uint64_t value) { for (auto &prop : props) { if (prop.name == propName) { prop.value = value; return; } } Q_UNREACHABLE(); } uint32_t MockObject::getPropId(const QString &propName) const { for (const auto &prop : std::as_const(props)) { if (prop.name == propName) { return prop.id; } } Q_UNREACHABLE(); } // MockProperty::MockProperty(MockObject *obj, QString name, uint64_t initialValue, uint32_t flags, QList enums) : obj(obj) , id(obj->gpu->idCounter++) , flags(flags) , name(name) , value(initialValue) , enums(enums) { qDebug("Added property %s with id %u to object %u", qPrintable(name), id, obj->id); } MockPropertyBlob::MockPropertyBlob(MockGpu *gpu, const void *d, size_t size) : gpu(gpu) , id(gpu->idCounter++) , data(malloc(size)) , size(size) { memcpy(data, d, size); } MockPropertyBlob::~MockPropertyBlob() { free(data); } // #define addProp(name, value, flags) props << MockProperty(this, QStringLiteral(name), value, flags) MockConnector::MockConnector(MockGpu *gpu, bool nonDesktop) : MockObject(gpu) , connection(DRM_MODE_CONNECTED) , type(DRM_MODE_CONNECTOR_DisplayPort) , encoder(std::make_shared(gpu, 0xFF)) { gpu->encoders << encoder; addProp("CRTC_ID", 0, DRM_MODE_PROP_ATOMIC); addProp("Subpixel", DRM_MODE_SUBPIXEL_UNKNOWN, DRM_MODE_PROP_IMMUTABLE); addProp("non-desktop", nonDesktop, DRM_MODE_PROP_IMMUTABLE); addProp("vrr_capable", 0, DRM_MODE_PROP_IMMUTABLE); addProp("DPMS", DRM_MODE_DPMS_OFF, 0); addProp("EDID", 0, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE); addMode(1920, 1080, 60.0); } void MockConnector::addMode(uint32_t width, uint32_t height, float refreshRate, bool preferred) { auto modeInfo = libxcvt_gen_mode_info(width, height, refreshRate, false, false); drmModeModeInfo mode{ .clock = uint32_t(modeInfo->dot_clock), .hdisplay = uint16_t(modeInfo->hdisplay), .hsync_start = modeInfo->hsync_start, .hsync_end = modeInfo->hsync_end, .htotal = modeInfo->htotal, .hskew = 0, .vdisplay = uint16_t(modeInfo->vdisplay), .vsync_start = modeInfo->vsync_start, .vsync_end = modeInfo->vsync_end, .vtotal = modeInfo->vtotal, .vscan = 1, .vrefresh = uint32_t(modeInfo->vrefresh), .flags = modeInfo->mode_flags, .type = DRM_MODE_TYPE_DRIVER, }; if (preferred) { mode.type |= DRM_MODE_TYPE_PREFERRED; } sprintf(mode.name, "%dx%d@%d", width, height, mode.vrefresh); modes.push_back(mode); free(modeInfo); } // MockCrtc::MockCrtc(MockGpu *gpu, const std::shared_ptr &legacyPlane, int pipeIndex, int gamma_size) : MockObject(gpu) , pipeIndex(pipeIndex) , gamma_size(gamma_size) , legacyPlane(legacyPlane) { addProp("MODE_ID", 0, DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_BLOB); addProp("ACTIVE", 0, DRM_MODE_PROP_ATOMIC); addProp("GAMMA_LUT", 0, DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_BLOB); addProp("GAMMA_LUT_SIZE", gamma_size, DRM_MODE_PROP_ATOMIC); } // MockPlane::MockPlane(MockGpu *gpu, PlaneType type, int crtcIndex) : MockObject(gpu) , possibleCrtcs(1 << crtcIndex) , type(type) { props << MockProperty(this, QStringLiteral("type"), static_cast(type), DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_ENUM, {QByteArrayLiteral("Primary"), QByteArrayLiteral("Overlay"), QByteArrayLiteral("Cursor")}); addProp("FB_ID", 0, DRM_MODE_PROP_ATOMIC); addProp("CRTC_ID", 0, DRM_MODE_PROP_ATOMIC); addProp("CRTC_X", 0, DRM_MODE_PROP_ATOMIC); addProp("CRTC_Y", 0, DRM_MODE_PROP_ATOMIC); addProp("CRTC_W", 0, DRM_MODE_PROP_ATOMIC); addProp("CRTC_H", 0, DRM_MODE_PROP_ATOMIC); addProp("SRC_X", 0, DRM_MODE_PROP_ATOMIC); addProp("SRC_Y", 0, DRM_MODE_PROP_ATOMIC); addProp("SRC_W", 0, DRM_MODE_PROP_ATOMIC); addProp("SRC_H", 0, DRM_MODE_PROP_ATOMIC); } // MockEncoder::MockEncoder(MockGpu* gpu, uint32_t possible_crtcs) : MockObject(gpu) , possible_crtcs(possible_crtcs) { } // MockFb::MockFb(MockGpu *gpu, uint32_t width, uint32_t height) : id(gpu->idCounter++) , width(width) , height(height) , gpu(gpu) { gpu->fbs << this; } MockFb::~MockFb() { gpu->fbs.removeOne(this); } // drm functions #define GPU(fd, error) \ auto gpu = getGpu(fd); \ if (!gpu) { \ qWarning("invalid fd %d", fd); \ errno = EINVAL; \ return error; \ } \ std::scoped_lock lock(gpu->m_mutex); drmVersionPtr drmGetVersion(int fd) { GPU(fd, nullptr); drmVersionPtr ptr = new drmVersion; ptr->name = new char[gpu->name.size() + 1]; strcpy(ptr->name, gpu->name.data()); return ptr; } void drmFreeVersion(drmVersionPtr ptr) { Q_ASSERT(ptr); delete[] ptr->name; delete ptr; } int drmSetClientCap(int fd, uint64_t capability, uint64_t value) { GPU(fd, -EINVAL); if (capability == DRM_CLIENT_CAP_ATOMIC) { if (!gpu->deviceCaps[MOCKDRM_DEVICE_CAP_ATOMIC]) { return -(errno = ENOTSUP); } qDebug("Setting DRM_CLIENT_CAP_ATOMIC to %lu", value); } gpu->clientCaps.insert(capability, value); return 0; } int drmGetCap(int fd, uint64_t capability, uint64_t *value) { GPU(fd, -EINVAL); if (gpu->deviceCaps.contains(capability)) { *value = gpu->deviceCaps[capability]; return 0; } qDebug("Could not find capability %lu", capability); return -(errno = EINVAL); } int drmHandleEvent(int fd, drmEventContextPtr evctx) { GPU(fd, -EINVAL); return -(errno = ENOTSUP); } int drmIoctl(int fd, unsigned long request, void *arg) { if (request == DRM_IOCTL_PRIME_FD_TO_HANDLE) { GPU(fd, -EINVAL); auto args = static_cast(arg); args->handle = 42; // just pass a dummy value so the request doesn't fail return 0; } else if (request == DRM_IOCTL_PRIME_HANDLE_TO_FD) { return -(errno = ENOTSUP); } else if (request == DRM_IOCTL_GEM_CLOSE) { GPU(fd, -EINVAL); return 0; } else if (request == DRM_IOCTL_MODE_ATOMIC) { const auto args = static_cast(arg); auto req = drmModeAtomicAlloc(); const uint32_t *const objects = reinterpret_cast(args->objs_ptr); const uint32_t *const propsCounts = reinterpret_cast(args->count_props_ptr); const uint32_t *const props = reinterpret_cast(args->props_ptr); const uint64_t *const values = reinterpret_cast(args->prop_values_ptr); uint32_t propIndex = 0; for (uint32_t objIndex = 0; objIndex < args->count_objs; objIndex++) { const uint32_t objectId = objects[objIndex]; const uint32_t count = propsCounts[objIndex]; for (uint32_t i = 0; i < count; i++) { drmModeAtomicAddProperty(req, objectId, props[propIndex + i], values[propIndex + i]); } propIndex += count; } int ret = drmModeAtomicCommit(fd, req, args->flags, reinterpret_cast(args->user_data)); drmModeAtomicFree(req); return ret; } else if (request == DRM_IOCTL_MODE_RMFB) { drmModeRmFB(fd, *static_cast(arg)); } return -(errno = ENOTSUP); } drmModeResPtr drmModeGetResources(int fd) { GPU(fd, nullptr) drmModeResPtr res = new drmModeRes; res->count_connectors = gpu->connectors.count(); res->connectors = res->count_connectors ? new uint32_t[res->count_connectors] : nullptr; int i = 0; for (const auto &conn : std::as_const(gpu->connectors)) { res->connectors[i++] = conn->id; } res->count_encoders = gpu->encoders.count(); res->encoders = res->count_encoders ? new uint32_t[res->count_encoders] : nullptr; i = 0; for (const auto &enc : std::as_const(gpu->encoders)) { res->encoders[i++] = enc->id; } res->count_crtcs = gpu->crtcs.count(); res->crtcs = res->count_crtcs ? new uint32_t[res->count_crtcs] : nullptr; i = 0; for (const auto &crtc : std::as_const(gpu->crtcs)) { res->crtcs[i++] = crtc->id; } res->count_fbs = gpu->fbs.count(); res->fbs = res->count_fbs ? new uint32_t[res->count_fbs] : nullptr; i = 0; for (const auto &fb : std::as_const(gpu->fbs)) { res->fbs[i++] = fb->id; } res->min_width = 0; res->min_height = 0; res->max_width = 2 << 14; res->max_height = 2 << 14; gpu->resPtrs << res; return res; } int drmModeAddFB(int fd, uint32_t width, uint32_t height, uint8_t depth, uint8_t bpp, uint32_t pitch, uint32_t bo_handle, uint32_t *buf_id) { GPU(fd, EINVAL) auto fb = new MockFb(gpu, width, height); *buf_id = fb->id; return 0; } int drmModeAddFB2(int fd, uint32_t width, uint32_t height, uint32_t pixel_format, const uint32_t bo_handles[4], const uint32_t pitches[4], const uint32_t offsets[4], uint32_t *buf_id, uint32_t flags) { GPU(fd, EINVAL) auto fb = new MockFb(gpu, width, height); *buf_id = fb->id; return 0; } int drmModeAddFB2WithModifiers(int fd, uint32_t width, uint32_t height, uint32_t pixel_format, const uint32_t bo_handles[4], const uint32_t pitches[4], const uint32_t offsets[4], const uint64_t modifier[4], uint32_t *buf_id, uint32_t flags) { GPU(fd, EINVAL) if (!gpu->deviceCaps.contains(DRM_CAP_ADDFB2_MODIFIERS)) { return -(errno = ENOTSUP); } auto fb = new MockFb(gpu, width, height); *buf_id = fb->id; return 0; } int drmModeRmFB(int fd, uint32_t bufferId) { GPU(fd, EINVAL) auto it = std::find_if(gpu->fbs.begin(), gpu->fbs.end(), [bufferId](const auto &fb){return fb->id == bufferId;}); if (it == gpu->fbs.end()) { qWarning("invalid bufferId %u passed to drmModeRmFB", bufferId); return EINVAL; } else { auto fb = *it; gpu->fbs.erase(it); for (const auto &plane : std::as_const(gpu->planes)) { if (plane->nextFb == fb) { plane->nextFb = nullptr; } if (plane->currentFb == fb) { qWarning("current fb %u of plane %u got removed. Deactivating plane", bufferId, plane->id); plane->setProp(QStringLiteral("CRTC_ID"), 0); plane->setProp(QStringLiteral("FB_ID"), 0); plane->currentFb = nullptr; auto crtc = gpu->findCrtc(plane->getProp(QStringLiteral("CRTC_ID"))); Q_ASSERT(crtc); crtc->setProp(QStringLiteral("ACTIVE"), 0); qWarning("deactvating crtc %u", crtc->id); for (const auto &conn : std::as_const(gpu->connectors)) { if (conn->getProp(QStringLiteral("CRTC_ID")) == crtc->id) { conn->setProp(QStringLiteral("CRTC_ID"), 0); qWarning("deactvating connector %u", conn->id); } } } } delete fb; return 0; } } drmModeCrtcPtr drmModeGetCrtc(int fd, uint32_t crtcId) { GPU(fd, nullptr); if (auto crtc = gpu->findCrtc(crtcId)) { drmModeCrtcPtr c = new drmModeCrtc; c->crtc_id = crtcId; c->buffer_id = crtc->currentFb ? crtc->currentFb->id : 0; c->gamma_size = crtc->gamma_size; c->mode_valid = crtc->modeValid; c->mode = crtc->mode; c->x = 0; c->y = 0; c->width = crtc->mode.hdisplay; c->height = crtc->mode.vdisplay; gpu->drmCrtcs << c; return c; } else { qWarning("invalid crtcId %u passed to drmModeGetCrtc", crtcId); errno = EINVAL; return nullptr; } } int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId, uint32_t x, uint32_t y, uint32_t *connectors, int count, drmModeModeInfoPtr mode) { GPU(fd, -EINVAL); auto crtc = gpu->findCrtc(crtcId); if (!crtc) { qWarning("invalid crtcId %u passed to drmModeSetCrtc", crtcId); return -(errno = EINVAL); } auto oldModeBlob = crtc->getProp(QStringLiteral("MODE_ID")); uint32_t modeBlob = 0; if (mode) { drmModeCreatePropertyBlob(fd, mode, sizeof(drmModeModeInfo), &modeBlob); } auto req = drmModeAtomicAlloc(); req->legacyEmulation = true; drmModeAtomicAddProperty(req, crtcId, crtc->getPropId(QStringLiteral("MODE_ID")), modeBlob); drmModeAtomicAddProperty(req, crtcId, crtc->getPropId(QStringLiteral("ACTIVE")), modeBlob && count); QList conns; for (int i = 0; i < count; i++) { conns << connectors[i]; } for (const auto &conn : std::as_const(gpu->connectors)) { if (conns.contains(conn->id)) { drmModeAtomicAddProperty(req, conn->id, conn->getPropId(QStringLiteral("CRTC_ID")), modeBlob ? crtc->id : 0); conns.removeOne(conn->id); } else if (conn->getProp(QStringLiteral("CRTC_ID")) == crtc->id) { drmModeAtomicAddProperty(req, conn->id, conn->getPropId(QStringLiteral("CRTC_ID")), 0); } } if (!conns.isEmpty()) { for (const auto &c : std::as_const(conns)) { qWarning("invalid connector %u passed to drmModeSetCrtc", c); } drmModeAtomicFree(req); return -(errno = EINVAL); } drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_ID")), modeBlob && count ? crtc->id : 0); drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_X")), x); drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_Y")), y); drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_W")), mode->hdisplay - x); drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("CRTC_H")), mode->vdisplay - y); drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("FB_ID")), bufferId); int result = drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, nullptr); drmModeAtomicFree(req); if (result == 0) { drmModeDestroyPropertyBlob(fd, oldModeBlob); } return result; } int drmModeSetCursor(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height) { GPU(fd, -EINVAL); if (auto crtc = gpu->findCrtc(crtcId)) { crtc->cursorRect.setSize(QSize(width, height)); return 0; } else { qWarning("invalid crtcId %u passed to drmModeSetCursor", crtcId); return -(errno = EINVAL); } } int drmModeSetCursor2(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height, int32_t hot_x, int32_t hot_y) { GPU(fd, -EINVAL); return -(errno = ENOTSUP); } int drmModeMoveCursor(int fd, uint32_t crtcId, int x, int y) { GPU(fd, -EINVAL); if (auto crtc = gpu->findCrtc(crtcId)) { crtc->cursorRect.moveTo(x, y); return 0; } else { qWarning("invalid crtcId %u passed to drmModeMoveCursor", crtcId); return -(errno = EINVAL); } } drmModeEncoderPtr drmModeGetEncoder(int fd, uint32_t encoder_id) { GPU(fd, nullptr); auto it = std::find_if(gpu->encoders.constBegin(), gpu->encoders.constEnd(), [encoder_id](const auto &e){return e->id == encoder_id;}); if (it == gpu->encoders.constEnd()) { qWarning("invalid encoder_id %u passed to drmModeGetEncoder", encoder_id); errno = EINVAL; return nullptr; } else { auto encoder = *it; drmModeEncoderPtr enc = new drmModeEncoder; enc->encoder_id = encoder_id; enc->crtc_id = encoder->crtc ? encoder->crtc->id : 0; enc->encoder_type = 0; enc->possible_crtcs = encoder->possible_crtcs; enc->possible_clones = encoder->possible_clones; gpu->drmEncoders << enc; return enc; } } // Instance ID of (some) specific connector type, incremented // for each new connector (of any type) being created. // There are no particular guarantees on the _stability_ of // connector type "instance IDs" issued by the kernel, // so simply giving each (new) connector a fresh ID is // acceptable. static std::atomic autoIncrementedConnectorId{}; drmModeConnectorPtr drmModeGetConnector(int fd, uint32_t connectorId) { GPU(fd, nullptr); if (auto conn = gpu->findConnector(connectorId)) { drmModeConnectorPtr c = new drmModeConnector{}; c->connector_id = conn->id; c->connection = conn->connection; c->connector_type = conn->type; c->connector_type_id = autoIncrementedConnectorId++; c->encoder_id = conn->encoder ? conn->encoder->id : 0; c->count_encoders = conn->encoder ? 1 : 0; c->encoders = c->count_encoders ? new uint32_t[1] : nullptr; if (c->encoders) { c->encoders[0] = conn->encoder->id; } c->count_modes = conn->modes.count(); c->modes = c->count_modes ? new drmModeModeInfo[c->count_modes] : nullptr; for (int i = 0; i < c->count_modes; i++) { c->modes[i] = conn->modes[i]; } c->mmHeight = 900; c->mmWidth = 1600; c->subpixel = DRM_MODE_SUBPIXEL_HORIZONTAL_RGB; // these are not used nor will they be c->count_props = -1; c->props = nullptr; c->prop_values = nullptr; gpu->drmConnectors << c; return c; } else { qWarning("invalid connectorId %u passed to drmModeGetConnector", connectorId); errno = EINVAL; return nullptr; } } drmModeConnectorPtr drmModeGetConnectorCurrent(int fd, uint32_t connector_id) { return drmModeGetConnector(fd, connector_id); } int drmModeCrtcSetGamma(int fd, uint32_t crtc_id, uint32_t size, uint16_t *red, uint16_t *green, uint16_t *blue) { return -(errno = ENOTSUP); } int drmModePageFlip(int fd, uint32_t crtc_id, uint32_t fb_id, uint32_t flags, void *user_data) { GPU(fd, -EINVAL); auto crtc = gpu->findCrtc(crtc_id); if (!crtc) { qWarning("invalid crtc_id %u passed to drmModePageFlip", crtc_id); return -(errno = EINVAL); } auto req = drmModeAtomicAlloc(); req->legacyEmulation = true; drmModeAtomicAddProperty(req, crtc->legacyPlane->id, crtc->legacyPlane->getPropId(QStringLiteral("FB_ID")), fb_id); int result = drmModeAtomicCommit(fd, req, flags, user_data); drmModeAtomicFree(req); return result; } drmModePlaneResPtr drmModeGetPlaneResources(int fd) { GPU(fd, nullptr); drmModePlaneResPtr res = new drmModePlaneRes; res->count_planes = gpu->planes.count(); res->planes = res->count_planes ? new uint32_t[res->count_planes] : nullptr; for (uint i = 0; i < res->count_planes; i++) { res->planes[i] = gpu->planes[i]->id; } gpu->drmPlaneRes << res; return res; } drmModePlanePtr drmModeGetPlane(int fd, uint32_t plane_id) { GPU(fd, nullptr); if (auto plane = gpu->findPlane(plane_id)) { drmModePlanePtr p = new drmModePlane; p->plane_id = plane_id; p->crtc_id = plane->getProp(QStringLiteral("CRTC_ID")); p->crtc_x = plane->getProp(QStringLiteral("CRTC_X")); p->crtc_y = plane->getProp(QStringLiteral("CRTC_Y")); p->fb_id = plane->getProp(QStringLiteral("FB_ID")); p->x = plane->getProp(QStringLiteral("SRC_X")); p->y = plane->getProp(QStringLiteral("SRC_Y")); p->possible_crtcs = plane->possibleCrtcs; // unused atm: p->count_formats = 0; p->formats = nullptr; p->gamma_size = 0; gpu->drmPlanes << p; return p; } else { qWarning("invalid plane_id %u passed to drmModeGetPlane", plane_id); errno = EINVAL; return nullptr; } } drmModePropertyPtr drmModeGetProperty(int fd, uint32_t propertyId) { GPU(fd, nullptr); for (const auto &obj : std::as_const(gpu->objects)) { for (auto &prop : std::as_const(obj->props)) { if (prop.id == propertyId) { drmModePropertyPtr p = new drmModePropertyRes; p->prop_id = prop.id; p->flags = prop.flags; auto arr = prop.name.toLocal8Bit(); strcpy(p->name, arr.constData()); p->count_blobs = prop.flags & DRM_MODE_PROP_BLOB ? 1 : 0; if (p->count_blobs) { p->blob_ids = new uint32_t[1]; p->blob_ids[0] = prop.value; } else { p->blob_ids = nullptr; } p->count_enums = prop.enums.count(); p->enums = new drm_mode_property_enum[p->count_enums]; for (int i = 0; i < p->count_enums; i++) { strcpy(p->enums[i].name, prop.enums[i].constData()); p->enums[i].value = i; } p->count_values = 1; p->values = new uint64_t[1]; p->values[0] = prop.value; gpu->drmProps << p; return p; } } } qWarning("invalid propertyId %u passed to drmModeGetProperty", propertyId); errno = EINVAL; return nullptr; } void drmModeFreeProperty(drmModePropertyPtr ptr) { if (!ptr) { return; } for (const auto &gpu : std::as_const(s_gpus)) { if (gpu->drmProps.removeOne(ptr)) { delete[] ptr->values; delete[] ptr->blob_ids; delete[] ptr->enums; delete ptr; return; } } } drmModePropertyBlobPtr drmModeGetPropertyBlob(int fd, uint32_t blob_id) { GPU(fd, nullptr); if (blob_id == 0) { return nullptr; } auto it = std::find_if(gpu->propertyBlobs.begin(), gpu->propertyBlobs.end(), [blob_id](const auto &blob) { return blob->id == blob_id; }); if (it == gpu->propertyBlobs.end()) { qWarning("invalid blob_id %u passed to drmModeGetPropertyBlob", blob_id); errno = EINVAL; return nullptr; } else { auto blob = new drmModePropertyBlobRes; blob->id = (*it)->id; blob->length = (*it)->size; blob->data = malloc(blob->length); memcpy(blob->data, (*it)->data, blob->length); return blob; } } void drmModeFreePropertyBlob(drmModePropertyBlobPtr ptr) { if (!ptr) { return; } for (const auto &gpu : std::as_const(s_gpus)) { if (gpu->drmPropertyBlobs.removeOne(ptr)) { free(ptr->data); delete ptr; return; } } } int drmModeConnectorSetProperty(int fd, uint32_t connector_id, uint32_t property_id, uint64_t value) { return drmModeObjectSetProperty(fd, connector_id, DRM_MODE_OBJECT_CONNECTOR, property_id, value); } static uint32_t getType(MockObject *obj) { if (dynamic_cast(obj)) { return DRM_MODE_OBJECT_CONNECTOR; } else if (dynamic_cast(obj)) { return DRM_MODE_OBJECT_CRTC; } else if (dynamic_cast(obj)) { return DRM_MODE_OBJECT_PLANE; } else { return DRM_MODE_OBJECT_ANY; } } drmModeObjectPropertiesPtr drmModeObjectGetProperties(int fd, uint32_t object_id, uint32_t object_type) { GPU(fd, nullptr); auto it = std::find_if(gpu->objects.constBegin(), gpu->objects.constEnd(), [object_id](const auto &obj){return obj->id == object_id;}); if (it == gpu->objects.constEnd()) { qWarning("invalid object_id %u passed to drmModeObjectGetProperties", object_id); errno = EINVAL; return nullptr; } else { auto obj = *it; if (auto type = getType(obj); type != object_type) { qWarning("wrong object_type %u passed to drmModeObjectGetProperties for object %u with type %u", object_type, object_id, type); errno = EINVAL; return nullptr; } QList props; bool deviceAtomic = gpu->clientCaps.contains(DRM_CLIENT_CAP_ATOMIC) && gpu->clientCaps[DRM_CLIENT_CAP_ATOMIC]; for (const auto &prop : std::as_const(obj->props)) { if (deviceAtomic || !(prop.flags & DRM_MODE_PROP_ATOMIC)) { props << prop; } } drmModeObjectPropertiesPtr p = new drmModeObjectProperties; p->count_props = props.count(); p->props = new uint32_t[p->count_props]; p->prop_values = new uint64_t[p->count_props]; int i = 0; for (const auto &prop : std::as_const(props)) { p->props[i] = prop.id; p->prop_values[i] = prop.value; i++; } gpu->drmObjectProperties << p; return p; } } void drmModeFreeObjectProperties(drmModeObjectPropertiesPtr ptr) { for (const auto &gpu : std::as_const(s_gpus)) { if (gpu->drmObjectProperties.removeOne(ptr)) { delete[] ptr->props; delete[] ptr->prop_values; delete ptr; return; } } } int drmModeObjectSetProperty(int fd, uint32_t object_id, uint32_t object_type, uint32_t property_id, uint64_t value) { GPU(fd, -EINVAL); auto it = std::find_if(gpu->objects.constBegin(), gpu->objects.constEnd(), [object_id](const auto &obj){return obj->id == object_id;}); if (it == gpu->objects.constEnd()) { qWarning("invalid object_id %u passed to drmModeObjectSetProperty", object_id); return -(errno = EINVAL); } else { auto obj = *it; if (auto type = getType(obj); type != object_type) { qWarning("wrong object_type %u passed to drmModeObjectSetProperty for object %u with type %u", object_type, object_id, type); return -(errno = EINVAL); } auto req = drmModeAtomicAlloc(); req->legacyEmulation = true; drmModeAtomicAddProperty(req, object_id, property_id, value); int result = drmModeAtomicCommit(fd, req, 0, nullptr); drmModeAtomicFree(req); return result; } } static QList s_atomicReqs; drmModeAtomicReqPtr drmModeAtomicAlloc(void) { auto req = new drmModeAtomicReq; s_atomicReqs << req; return req; } void drmModeAtomicFree(drmModeAtomicReqPtr req) { s_atomicReqs.removeOne(req); delete req; } int drmModeAtomicAddProperty(drmModeAtomicReqPtr req, uint32_t object_id, uint32_t property_id, uint64_t value) { if (!req) { return -(errno = EINVAL); } Prop p; p.obj = object_id; p.prop = property_id; p.value = value; req->props << p; return req->props.count(); } static bool checkIfEqual(const drmModeModeInfo &one, const drmModeModeInfo &two) { return one.clock == two.clock && one.hdisplay == two.hdisplay && one.hsync_start == two.hsync_start && one.hsync_end == two.hsync_end && one.htotal == two.htotal && one.hskew == two.hskew && one.vdisplay == two.vdisplay && one.vsync_start == two.vsync_start && one.vsync_end == two.vsync_end && one.vtotal == two.vtotal && one.vscan == two.vscan && one.vrefresh == two.vrefresh; } int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req, uint32_t flags, void *user_data) { GPU(fd, -EINVAL); if (!req->legacyEmulation && (!gpu->clientCaps.contains(DRM_CLIENT_CAP_ATOMIC) || !gpu->clientCaps[DRM_CLIENT_CAP_ATOMIC])) { qWarning("drmModeAtomicCommit requires the atomic capability"); return -(errno = EINVAL); } // verify flags if ((flags & DRM_MODE_ATOMIC_NONBLOCK) && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { qWarning() << "NONBLOCK and MODESET are not allowed together"; return -(errno = EINVAL); } else if ((flags & DRM_MODE_ATOMIC_TEST_ONLY) && (flags & DRM_MODE_PAGE_FLIP_EVENT)) { qWarning() << "TEST_ONLY and PAGE_FLIP_EVENT are not allowed together"; return -(errno = EINVAL); } else if (flags & DRM_MODE_PAGE_FLIP_ASYNC) { qWarning() << "PAGE_FLIP_ASYNC is currently not supported with AMS"; return -(errno = EINVAL); } QList connCopies; for (const auto &conn : std::as_const(gpu->connectors)) { connCopies << *conn; } QList crtcCopies; for (const auto &crtc : std::as_const(gpu->crtcs)) { crtcCopies << *crtc; } QList planeCopies; for (const auto &plane : std::as_const(gpu->planes)) { planeCopies << *plane; } QList objects; for (int i = 0; i < connCopies.count(); i++) { objects << &connCopies[i]; } for (int i = 0; i < crtcCopies.count(); i++) { objects << &crtcCopies[i]; } for (int i = 0; i < planeCopies.count(); i++) { objects << &planeCopies[i]; } // apply changes to the copies for (int i = 0; i < req->props.count(); i++) { auto p = req->props[i]; auto it = std::find_if(objects.constBegin(), objects.constEnd(), [p](const auto &obj){return obj->id == p.obj;}); if (it == objects.constEnd()) { qWarning("Object %u in atomic request not found!", p.obj); return -(errno = EINVAL); } auto &obj = *it; if (obj->id == p.obj) { auto prop = std::find_if(obj->props.begin(), obj->props.end(), [p](const auto &prop){return prop.id == p.prop;}); if (prop == obj->props.end()) { qWarning("Property %u in atomic request for object %u not found!", p.prop, p.obj); return -(errno = EINVAL); } if (prop->value != p.value) { if (!(flags & DRM_MODE_ATOMIC_ALLOW_MODESET) && (prop->name == QStringLiteral("CRTC_ID") || prop->name == QStringLiteral("ACTIVE"))) { qWarning("Atomic request without DRM_MODE_ATOMIC_ALLOW_MODESET tries to do a modeset with object %u", obj->id); return -(errno = EINVAL); } if (prop->flags & DRM_MODE_PROP_BLOB) { auto blobExists = gpu->getBlob(p.value) != nullptr; if (blobExists != (p.value > 0)) { qWarning("Atomic request tries to set property %s on obj %u to invalid blob id %lu", qPrintable(prop->name), obj->id, p.value); return -(errno = EINVAL); } } prop->value = p.value; } } } // check if the desired changes are allowed struct Pipeline { MockCrtc *crtc; QList conns; MockPlane *primaryPlane = nullptr; }; QList pipelines; for (int i = 0; i < crtcCopies.count(); i++) { if (crtcCopies[i].getProp(QStringLiteral("ACTIVE"))) { auto blob = gpu->getBlob(crtcCopies[i].getProp(QStringLiteral("MODE_ID"))); if (!blob) { qWarning("Atomic request tries to enable CRTC %u without a mode", crtcCopies[i].id); return -(errno = EINVAL); } else if (blob->size != sizeof(drmModeModeInfo)) { qWarning("Atomic request tries to enable CRTC %u with an invalid mode blob", crtcCopies[i].id); return -(errno = EINVAL); } Pipeline pipeline; pipeline.crtc = &crtcCopies[i]; pipelines << pipeline; } } for (int i = 0; i < connCopies.count(); i++) { if (auto crtc = connCopies[i].getProp(QStringLiteral("CRTC_ID"))) { bool found = false; for (int p = 0; p < pipelines.count(); p++) { if (pipelines[p].crtc->id == crtc) { pipelines[p].conns << &connCopies[i]; found = true; break; } } if (!found) { qWarning("CRTC_ID of connector %u points to inactive or wrong crtc", connCopies[i].id); return -(errno = EINVAL); } } } for (int i = 0; i < planeCopies.count(); i++) { if (auto crtc = planeCopies[i].getProp(QStringLiteral("CRTC_ID"))) { bool found = false; for (int p = 0; p < pipelines.count(); p++) { if (pipelines[p].crtc->id == crtc) { if (pipelines[p].primaryPlane) { qWarning("crtc %u has more than one primary planes assigned: %u and %u", pipelines[p].crtc->id, pipelines[p].primaryPlane->id, planeCopies[i].id); return -(errno = EINVAL); } else if (!(planeCopies[i].possibleCrtcs & (1 << pipelines[p].crtc->pipeIndex))) { qWarning("crtc %u is not suitable for primary plane %u", pipelines[p].crtc->id, planeCopies[i].id); return -(errno = EINVAL); } else { pipelines[p].primaryPlane = &planeCopies[i]; found = true; break; } } } if (!found) { qWarning("CRTC_ID of plane %u points to inactive or wrong crtc", planeCopies[i].id); return -(errno = EINVAL); } auto fbId = planeCopies[i].getProp(QStringLiteral("FB_ID")); if (!fbId) { qWarning("FB_ID of active plane %u is 0", planeCopies[i].id); return -(errno = EINVAL); } auto it = std::find_if(gpu->fbs.constBegin(), gpu->fbs.constEnd(), [fbId](auto fb){return fb->id == fbId;}); if (it == gpu->fbs.constEnd()) { qWarning("FB_ID %lu of active plane %u is invalid", fbId, planeCopies[i].id); return -(errno = EINVAL); } planeCopies[i].nextFb = *it; } else { planeCopies[i].nextFb = nullptr; } } for (const auto &p : std::as_const(pipelines)) { if (p.conns.isEmpty()) { qWarning("Active crtc %u has no assigned connectors", p.crtc->id); return -(errno = EINVAL); } else if (!p.primaryPlane) { qWarning("Active crtc %u has no assigned primary plane", p.crtc->id); return -(errno = EINVAL); } else { drmModeModeInfo mode = *static_cast(gpu->getBlob(p.crtc->getProp(QStringLiteral("MODE_ID")))->data); for (const auto &conn : p.conns) { bool modeFound = std::find_if(conn->modes.constBegin(), conn->modes.constEnd(), [mode](const auto &m){ return checkIfEqual(mode, m); }) != conn->modes.constEnd(); if (!modeFound) { qWarning("mode on crtc %u is incompatible with connector %u", p.crtc->id, conn->id); return -(errno = EINVAL); } } } } // if wanted, apply them if (!(flags & DRM_MODE_ATOMIC_TEST_ONLY)) { for (auto &conn : std::as_const(gpu->connectors)) { auto it = std::find_if(connCopies.constBegin(), connCopies.constEnd(), [conn](auto c){return c.id == conn->id;}); if (it == connCopies.constEnd()) { qCritical("implementation error: can't find connector %u", conn->id); return -(errno = EINVAL); } *conn = *it; } for (auto &crtc : std::as_const(gpu->crtcs)) { auto it = std::find_if(crtcCopies.constBegin(), crtcCopies.constEnd(), [crtc](auto c){return c.id == crtc->id;}); if (it == crtcCopies.constEnd()) { qCritical("implementation error: can't find crtc %u", crtc->id); return -(errno = EINVAL); } *crtc = *it; } for (auto &plane : std::as_const(gpu->planes)) { auto it = std::find_if(planeCopies.constBegin(), planeCopies.constEnd(), [plane](auto c){return c.id == plane->id;}); if (it == planeCopies.constEnd()) { qCritical("implementation error: can't find plane %u", plane->id); return -(errno = EINVAL); } *plane = *it; } if (flags & DRM_MODE_PAGE_FLIP_EVENT) { // Unsupported } } return 0; } int drmModeCreatePropertyBlob(int fd, const void *data, size_t size, uint32_t *id) { GPU(fd, -EINVAL); if (!data || !size || !id) { return -(errno = EINVAL); } auto blob = std::make_unique(gpu, data, size); *id = blob->id; gpu->propertyBlobs.push_back(std::move(blob)); return 0; } int drmModeDestroyPropertyBlob(int fd, uint32_t id) { GPU(fd, -EINVAL); auto it = std::remove_if(gpu->propertyBlobs.begin(), gpu->propertyBlobs.end(), [id](const auto &blob) { return blob->id == id; }); if (it == gpu->propertyBlobs.end()) { return -(errno = EINVAL); } else { gpu->propertyBlobs.erase(it, gpu->propertyBlobs.end()); return 0; } } int drmModeCreateLease(int fd, const uint32_t *objects, int num_objects, int flags, uint32_t *lessee_id) { return -(errno = ENOTSUP); } drmModeLesseeListPtr drmModeListLessees(int fd) { return nullptr; } drmModeObjectListPtr drmModeGetLease(int fd) { return nullptr; } int drmModeRevokeLease(int fd, uint32_t lessee_id) { return -(errno = ENOTSUP); } void drmModeFreeResources(drmModeResPtr ptr) { for (const auto &gpu : std::as_const(s_gpus)) { if (gpu->resPtrs.removeOne(ptr)) { delete[] ptr->connectors; delete[] ptr->crtcs; delete[] ptr->encoders; delete[] ptr->fbs; delete ptr; } } } void drmModeFreePlaneResources(drmModePlaneResPtr ptr) { for (const auto &gpu : std::as_const(s_gpus)) { if (gpu->drmPlaneRes.removeOne(ptr)) { delete[] ptr->planes; delete ptr; } } } void drmModeFreeCrtc(drmModeCrtcPtr ptr) { for (const auto &gpu : std::as_const(s_gpus)) { if (gpu->drmCrtcs.removeOne(ptr)) { delete ptr; return; } } Q_UNREACHABLE(); } void drmModeFreeConnector(drmModeConnectorPtr ptr) { for (const auto &gpu : std::as_const(s_gpus)) { if (gpu->drmConnectors.removeOne(ptr)) { delete[] ptr->encoders; delete[] ptr->props; delete[] ptr->prop_values; delete ptr; return; } } Q_UNREACHABLE(); } void drmModeFreeEncoder(drmModeEncoderPtr ptr) { for (const auto &gpu : std::as_const(s_gpus)) { if (gpu->drmEncoders.removeOne(ptr)) { delete ptr; return; } } Q_UNREACHABLE(); } void drmModeFreePlane(drmModePlanePtr ptr) { for (const auto &gpu : std::as_const(s_gpus)) { if (gpu->drmPlanes.removeOne(ptr)) { delete ptr; return; } } Q_UNREACHABLE(); }