Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qkmsdevice.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// Copyright (C) 2016 Pelagicore AG
3// Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5
6#include "qkmsdevice_p.h"
7
8#include <QtCore/QJsonDocument>
9#include <QtCore/QJsonObject>
10#include <QtCore/QJsonArray>
11#include <QtCore/QFile>
12#include <QtCore/QLoggingCategory>
13
14#include <errno.h>
15
16#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
17
18QT_BEGIN_NAMESPACE
19
20using namespace Qt::StringLiterals;
21
22Q_STATIC_LOGGING_CATEGORY(qLcKmsDebug, "qt.qpa.eglfs.kms")
23
32
33int QKmsDevice::crtcForConnector(drmModeResPtr resources, drmModeConnectorPtr connector)
34{
35 int candidate = -1;
36
37 // m_crtc_allocator is a 32 bit mask, so CRTCs past the first 32 cannot be
38 // tracked and are not considered.
39 const int crtcCount = qMin(resources->count_crtcs, 32);
40
41 for (int i = 0; i < connector->count_encoders; i++) {
42 drmModeEncoderPtr encoder = drmModeGetEncoder(m_dri_fd, connector->encoders[i]);
43 if (!encoder) {
44 qWarning("Failed to get encoder");
45 continue;
46 }
47
48 quint32 encoderId = encoder->encoder_id;
49 quint32 crtcId = encoder->crtc_id;
50 quint32 possibleCrtcs = encoder->possible_crtcs;
51 drmModeFreeEncoder(encoder);
52
53 for (int j = 0; j < crtcCount; j++) {
54 bool isPossible = possibleCrtcs & (1u << j);
55 bool isAvailable = !(m_crtc_allocator & (1u << j));
56 // Preserve the existing CRTC -> encoder -> connector routing if
57 // any. It makes the initialization faster, and may be better
58 // since we have a very dumb picking algorithm.
59 bool isBestChoice = (!connector->encoder_id ||
60 (connector->encoder_id == encoderId &&
61 resources->crtcs[j] == crtcId));
62
63 if (isPossible && isAvailable && isBestChoice) {
64 return j;
65 } else if (isPossible && isAvailable) {
66 candidate = j;
67 }
68 }
69 }
70
71 return candidate;
72}
73
74static const char * const connector_type_names[] = { // must match DRM_MODE_CONNECTOR_*
75 "None",
76 "VGA",
77 "DVI",
78 "DVI",
79 "DVI",
80 "Composite",
81 "TV",
82 "LVDS",
83 "CTV",
84 "DIN",
85 "DP",
86 "HDMI",
87 "HDMI",
88 "TV",
89 "eDP",
90 "Virtual",
91 "DSI"
92};
93
94static QByteArray nameForConnector(const drmModeConnectorPtr connector)
95{
96 QByteArray connectorName("UNKNOWN");
97
98 if (connector->connector_type < ARRAY_LENGTH(connector_type_names))
99 connectorName = connector_type_names[connector->connector_type];
100
101 connectorName += QByteArray::number(connector->connector_type_id);
102
103 return connectorName;
104}
105
106static bool parseModeline(const QByteArray &text, drmModeModeInfoPtr mode)
107{
108 char hsync[16];
109 char vsync[16];
110 float fclock;
111
112 // Zero everything, not just the fields set below: the mode is handed to the
113 // kernel as a property blob, name included.
114 memset(mode, 0, sizeof(*mode));
115 mode->type = DRM_MODE_TYPE_USERDEF;
116
117 if (sscanf(text.constData(), "%f %hu %hu %hu %hu %hu %hu %hu %hu %15s %15s",
118 &fclock,
119 &mode->hdisplay,
120 &mode->hsync_start,
121 &mode->hsync_end,
122 &mode->htotal,
123 &mode->vdisplay,
124 &mode->vsync_start,
125 &mode->vsync_end,
126 &mode->vtotal, hsync, vsync) != 11)
127 return false;
128
129 mode->clock = fclock * 1000;
130
131 if (strcmp(hsync, "+hsync") == 0)
132 mode->flags |= DRM_MODE_FLAG_PHSYNC;
133 else if (strcmp(hsync, "-hsync") == 0)
134 mode->flags |= DRM_MODE_FLAG_NHSYNC;
135 else
136 return false;
137
138 if (strcmp(vsync, "+vsync") == 0)
139 mode->flags |= DRM_MODE_FLAG_PVSYNC;
140 else if (strcmp(vsync, "-vsync") == 0)
141 mode->flags |= DRM_MODE_FLAG_NVSYNC;
142 else
143 return false;
144
145 return true;
146}
147
148static inline void assignPlane(QKmsOutput *output, QKmsPlane *plane)
149{
150 if (output->eglfs_plane)
152
153 plane->activeCrtcId = output->crtc_id;
154 output->eglfs_plane = plane;
155}
156
158 const QKmsDevice::OrderedScreen &b)
159{
160 return a.vinfo.virtualIndex < b.vinfo.virtualIndex;
161}
162
164
165QKmsDevice::OrderedScreen::OrderedScreen(QPlatformScreen *screen,
166 const QKmsDevice::ScreenInfo &vinfo)
168{
169}
170
171QDebug operator<<(QDebug dbg, const QPlatformScreen *screen)
172{
173 QDebugStateSaver saver(dbg);
174 dbg.nospace() << "QPlatformScreen=" << (const void *)screen << " ("
175 << (screen ? screen->name() : QString()) << ")";
176 return dbg;
177}
178
179QDebug operator<<(QDebug dbg, const QKmsDevice::OrderedScreen &s)
180{
181 QDebugStateSaver saver(dbg);
182 dbg.nospace() << "OrderedScreen(" << s.screen << ") : " << s.vinfo.virtualIndex << " / "
183 << s.vinfo.virtualPos << " / primary: " << s.vinfo.isPrimary << ")";
184 return dbg;
185}
186
187bool QKmsDevice::createScreenInfoForConnector(drmModeResPtr resources,
188 drmModeConnectorPtr connector, ScreenInfo &vinfo)
189{
190 const QByteArray connectorName = nameForConnector(connector);
191
192 const int crtc = crtcForConnector(resources, connector);
193 if (crtc < 0) {
194 qWarning() << "No usable crtc/encoder pair for connector" << connectorName;
195 return false;
196 }
197
198 OutputConfiguration configuration;
199 QSize configurationSize;
200 int configurationRefresh = 0;
201 drmModeModeInfo configurationModeline;
202
203 auto userConfig = m_screenConfig->outputSettings();
204 QVariantMap userConnectorConfig = userConfig.value(QString::fromUtf8(connectorName));
205 // default to the preferred mode unless overridden in the config
206 const QByteArray mode = userConnectorConfig.value(QStringLiteral("mode"), QStringLiteral("preferred"))
207 .toByteArray().toLower();
208 if (mode == "off") {
209 configuration = OutputConfigOff;
210 } else if (mode == "preferred") {
211 configuration = OutputConfigPreferred;
212 } else if (mode == "current") {
213 configuration = OutputConfigCurrent;
214 } else if (mode == "skip") {
215 configuration = OutputConfigSkip;
216 } else if (sscanf(mode.constData(), "%dx%d@%d", &configurationSize.rwidth(), &configurationSize.rheight(),
217 &configurationRefresh) == 3)
218 {
219 configuration = OutputConfigMode;
220 } else if (sscanf(mode.constData(), "%dx%d", &configurationSize.rwidth(), &configurationSize.rheight()) == 2) {
221 configuration = OutputConfigMode;
222 } else if (parseModeline(mode, &configurationModeline)) {
223 configuration = OutputConfigModeline;
224 } else {
225 qWarning("Invalid mode \"%s\" for output %s", mode.constData(), connectorName.constData());
226 configuration = OutputConfigPreferred;
227 }
228
229 vinfo.virtualIndex = userConnectorConfig.value(QStringLiteral("virtualIndex"), INT_MAX).toInt();
230 if (userConnectorConfig.contains(QStringLiteral("virtualPos"))) {
231 const QByteArray vpos = userConnectorConfig.value(QStringLiteral("virtualPos")).toByteArray();
232 const QByteArrayList vposComp = vpos.split(',');
233 if (vposComp.count() == 2) {
234 vinfo.virtualPos = QPoint(vposComp[0].trimmed().toInt(), vposComp[1].trimmed().toInt());
235 qCDebug(qLcKmsDebug) << "Parsing virtualPos to: " << vinfo.virtualPos;
236 } else {
237 vinfo.virtualPos = QPoint(-1, -1);
238 qCDebug(qLcKmsDebug) << "Could not parse virtualPos,"
239 << "will be calculated based on virtualIndex";
240 }
241 } else {
242 vinfo.virtualPos = QPoint(-1, -1);
243 }
244
245 if (userConnectorConfig.value(QStringLiteral("primary")).toBool())
246 vinfo.isPrimary = true;
247
248 const uint32_t crtc_id = resources->crtcs[crtc];
249
250 if (configuration == OutputConfigOff) {
251 qCDebug(qLcKmsDebug) << "Turning off output" << connectorName;
252 drmModeSetCrtc(m_dri_fd, crtc_id, 0, 0, 0, 0, 0, nullptr);
253 return false;
254 }
255
256 // Skip disconnected output
257 if (configuration == OutputConfigPreferred && connector->connection == DRM_MODE_DISCONNECTED) {
258 qCDebug(qLcKmsDebug) << "Skipping disconnected output" << connectorName;
259 return false;
260 }
261
262 if (configuration == OutputConfigSkip) {
263 qCDebug(qLcKmsDebug) << "Skipping output" << connectorName;
264 return false;
265 }
266
267 // Get the current mode on the current crtc
268 drmModeModeInfo crtc_mode;
269 memset(&crtc_mode, 0, sizeof crtc_mode);
270 if (drmModeEncoderPtr encoder = drmModeGetEncoder(m_dri_fd, connector->encoder_id)) {
271
272 drmModeCrtcPtr crtc = drmModeGetCrtc(m_dri_fd, encoder->crtc_id);
273 drmModeFreeEncoder(encoder);
274
275 if (!crtc)
276 return false;
277
278 if (crtc->mode_valid)
279 crtc_mode = crtc->mode;
280
281 drmModeFreeCrtc(crtc);
282 }
283
284 QList<drmModeModeInfo> modes;
285 modes.reserve(connector->count_modes);
286 qCDebug(qLcKmsDebug) << connectorName << "mode count:" << connector->count_modes
287 << "crtc index:" << crtc << "crtc id:" << crtc_id;
288 for (int i = 0; i < connector->count_modes; i++) {
289 const drmModeModeInfo &mode = connector->modes[i];
290 qCDebug(qLcKmsDebug) << "mode" << i << mode.hdisplay << "x" << mode.vdisplay
291 << '@' << mode.vrefresh << "hz";
292 modes << connector->modes[i];
293 }
294
295 int preferred = -1;
296 int current = -1;
297 int configured = -1;
298 int best = -1;
299
300 for (int i = modes.size() - 1; i >= 0; i--) {
301 const drmModeModeInfo &m = modes.at(i);
302
303 if (configuration == OutputConfigMode
304 && m.hdisplay == configurationSize.width()
305 && m.vdisplay == configurationSize.height()
306 && (!configurationRefresh || m.vrefresh == uint32_t(configurationRefresh)))
307 {
308 configured = i;
309 }
310
311 if (!memcmp(&crtc_mode, &m, sizeof m))
312 current = i;
313
314 if (m.type & DRM_MODE_TYPE_PREFERRED)
315 preferred = i;
316
317 best = i;
318 }
319
320 if (configuration == OutputConfigModeline) {
321 modes << configurationModeline;
322 configured = modes.size() - 1;
323 }
324
325 if (current < 0 && crtc_mode.clock != 0) {
326 modes << crtc_mode;
327 current = modes.size() - 1;
328 }
329
330 if (configuration == OutputConfigCurrent)
331 configured = current;
332
333 int selected_mode = -1;
334
335 if (configured >= 0)
336 selected_mode = configured;
337 else if (preferred >= 0)
338 selected_mode = preferred;
339 else if (current >= 0)
340 selected_mode = current;
341 else if (best >= 0)
342 selected_mode = best;
343
344 if (selected_mode < 0) {
345 qWarning() << "No modes available for output" << connectorName;
346 return false;
347 } else {
348 int width = modes[selected_mode].hdisplay;
349 int height = modes[selected_mode].vdisplay;
350 int refresh = modes[selected_mode].vrefresh;
351 qCDebug(qLcKmsDebug) << "Selected mode" << selected_mode << ":" << width << "x" << height
352 << '@' << refresh << "hz for output" << connectorName;
353 }
354
355 // physical size from connector < config values < env vars
356 int pwidth = qEnvironmentVariableIntValue("QT_QPA_EGLFS_PHYSICAL_WIDTH");
357 if (!pwidth)
358 pwidth = qEnvironmentVariableIntValue("QT_QPA_PHYSICAL_WIDTH");
359 int pheight = qEnvironmentVariableIntValue("QT_QPA_EGLFS_PHYSICAL_HEIGHT");
360 if (!pheight)
361 pheight = qEnvironmentVariableIntValue("QT_QPA_PHYSICAL_HEIGHT");
362 QSizeF physSize(pwidth, pheight);
363 if (physSize.isEmpty()) {
364 physSize = QSize(userConnectorConfig.value(QStringLiteral("physicalWidth")).toInt(),
365 userConnectorConfig.value(QStringLiteral("physicalHeight")).toInt());
366 if (physSize.isEmpty()) {
367 physSize.setWidth(connector->mmWidth);
368 physSize.setHeight(connector->mmHeight);
369 }
370 }
371 qCDebug(qLcKmsDebug) << "Physical size is" << physSize << "mm" << "for output" << connectorName;
372
373 const QByteArray formatStr = userConnectorConfig.value(QStringLiteral("format"), QString())
374 .toByteArray().toLower();
375 uint32_t drmFormat;
376 bool drmFormatExplicit = true;
377 if (formatStr.isEmpty()) {
378 drmFormat = DRM_FORMAT_XRGB8888;
379 drmFormatExplicit = false;
380 } else if (formatStr == "xrgb8888") {
381 drmFormat = DRM_FORMAT_XRGB8888;
382 } else if (formatStr == "xbgr8888") {
383 drmFormat = DRM_FORMAT_XBGR8888;
384 } else if (formatStr == "argb8888") {
385 drmFormat = DRM_FORMAT_ARGB8888;
386 } else if (formatStr == "abgr8888") {
387 drmFormat = DRM_FORMAT_ABGR8888;
388 } else if (formatStr == "rgb565") {
389 drmFormat = DRM_FORMAT_RGB565;
390 } else if (formatStr == "bgr565") {
391 drmFormat = DRM_FORMAT_BGR565;
392 } else if (formatStr == "xrgb2101010") {
393 drmFormat = DRM_FORMAT_XRGB2101010;
394 } else if (formatStr == "xbgr2101010") {
395 drmFormat = DRM_FORMAT_XBGR2101010;
396 } else if (formatStr == "argb2101010") {
397 drmFormat = DRM_FORMAT_ARGB2101010;
398 } else if (formatStr == "abgr2101010") {
399 drmFormat = DRM_FORMAT_ABGR2101010;
400 } else {
401 qWarning("Invalid pixel format \"%s\" for output %s", formatStr.constData(), connectorName.constData());
402 drmFormat = DRM_FORMAT_XRGB8888;
403 drmFormatExplicit = false;
404 }
405 qCDebug(qLcKmsDebug) << "Format is" << Qt::hex << drmFormat << Qt::dec << "requested_by_user =" << drmFormatExplicit
406 << "for output" << connectorName;
407
408 const QString cloneSource = userConnectorConfig.value(QStringLiteral("clones")).toString();
409 if (!cloneSource.isEmpty())
410 qCDebug(qLcKmsDebug) << "Output" << connectorName << " clones output " << cloneSource;
411
412 QSize framebufferSize;
413 bool framebufferSizeSet = false;
414 const QByteArray fbsize = userConnectorConfig.value(QStringLiteral("size")).toByteArray().toLower();
415 if (!fbsize.isEmpty()) {
416 if (sscanf(fbsize.constData(), "%dx%d", &framebufferSize.rwidth(), &framebufferSize.rheight()) == 2) {
417#if QT_CONFIG(drm_atomic)
418 if (hasAtomicSupport())
419 framebufferSizeSet = true;
420#endif
421 if (!framebufferSizeSet)
422 qWarning("Setting framebuffer size is only available with DRM atomic API");
423 } else {
424 qWarning("Invalid framebuffer size '%s'", fbsize.constData());
425 }
426 }
427 if (!framebufferSizeSet) {
428 framebufferSize.setWidth(modes[selected_mode].hdisplay);
429 framebufferSize.setHeight(modes[selected_mode].vdisplay);
430 }
431
432 qCDebug(qLcKmsDebug) << "Output" << connectorName << "framebuffer size is " << framebufferSize;
433
434 QKmsOutput output;
435 output.name = QString::fromUtf8(connectorName);
436 output.connector_id = connector->connector_id;
437 output.crtc_index = crtc;
438 output.crtc_id = crtc_id;
439 output.physical_size = physSize;
440 output.preferred_mode = preferred >= 0 ? preferred : selected_mode;
441 output.mode = selected_mode;
442 output.mode_set = false;
443 output.saved_crtc = drmModeGetCrtc(m_dri_fd, crtc_id);
444 output.modes = modes;
445 output.subpixel = connector->subpixel;
446 output.dpms_prop = connectorProperty(connector, QByteArrayLiteral("DPMS"));
447 output.edid_blob = connectorPropertyBlob(connector, QByteArrayLiteral("EDID"));
448 output.wants_forced_plane = false;
449 output.forced_plane_id = 0;
450 output.forced_plane_set = false;
451 output.drm_format = drmFormat;
452 output.drm_format_requested_by_user = drmFormatExplicit;
453 output.clone_source = cloneSource;
454 output.size = framebufferSize;
455
456#if QT_CONFIG(drm_atomic)
457 if (drmModeCreatePropertyBlob(m_dri_fd, &modes[selected_mode], sizeof(drmModeModeInfo),
458 &output.mode_blob_id) != 0) {
459 qCDebug(qLcKmsDebug) << "Failed to create mode blob for mode" << selected_mode;
460 }
461
462 parseConnectorProperties(output.connector_id, &output);
463 parseCrtcProperties(output.crtc_id, &output);
464#endif
465
466 QString planeListStr;
467 for (QKmsPlane &plane : m_planes) {
468 if (plane.possibleCrtcs & (1u << output.crtc_index)) {
469 output.available_planes.append(plane);
470 planeListStr.append(QString::number(plane.id));
471 planeListStr.append(u' ');
472
473 // Choose the first primary plane that is not already assigned to
474 // another screen's associated crtc.
475 if (!output.eglfs_plane && plane.type == QKmsPlane::PrimaryPlane && !plane.activeCrtcId)
476 assignPlane(&output, &plane);
477 }
478 }
479 qCDebug(qLcKmsDebug, "Output %s can use %d planes: %s",
480 connectorName.constData(), int(output.available_planes.size()), qPrintable(planeListStr));
481
482 // This is for the EGLDevice/EGLStream backend. On some of those devices one
483 // may want to target a pre-configured plane. It is probably useless for
484 // eglfs_kms and others. Do not confuse with generic plane support (available_planes).
485 bool ok;
486 int idx = qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_PLANE_INDEX", &ok);
487 if (ok) {
488 drmModePlaneRes *planeResources = drmModeGetPlaneResources(m_dri_fd);
489 if (planeResources) {
490 if (idx >= 0 && idx < int(planeResources->count_planes)) {
491 drmModePlane *plane = drmModeGetPlane(m_dri_fd, planeResources->planes[idx]);
492 if (plane) {
493 output.wants_forced_plane = true;
494 output.forced_plane_id = plane->plane_id;
495 qCDebug(qLcKmsDebug, "Forcing plane index %d, plane id %u (belongs to crtc id %u)",
496 idx, plane->plane_id, plane->crtc_id);
497
498 for (QKmsPlane &kmsplane : m_planes) {
499 if (kmsplane.id == output.forced_plane_id) {
500 assignPlane(&output, &kmsplane);
501 break;
502 }
503 }
504
505 drmModeFreePlane(plane);
506 }
507 } else {
508 qWarning("Invalid plane index %d, must be between 0 and %u", idx, planeResources->count_planes - 1);
509 }
510 }
511 }
512
513 // A more useful version: allows specifying "crtc_id,plane_id:crtc_id,plane_id:..."
514 // in order to allow overriding the plane used for a given crtc.
515 if (qEnvironmentVariableIsSet("QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS")) {
516 const QString val = qEnvironmentVariable("QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS");
517 qCDebug(qLcKmsDebug, "crtc_id:plane_id override list: %s", qPrintable(val));
518 const QStringList crtcPlanePairs = val.split(u':');
519 for (const QString &crtcPlanePair : crtcPlanePairs) {
520 const QStringList values = crtcPlanePair.split(u',');
521 if (values.size() == 2 && uint(values[0].toInt()) == output.crtc_id) {
522 uint planeId = values[1].toInt();
523 for (QKmsPlane &kmsplane : m_planes) {
524 if (kmsplane.id == planeId) {
525 assignPlane(&output, &kmsplane);
526 break;
527 }
528 }
529 }
530 }
531 }
532
533 if (output.eglfs_plane) {
534 qCDebug(qLcKmsDebug, "Chose plane %u for output %s (crtc id %u) (may not be applicable)",
535 output.eglfs_plane->id, connectorName.constData(), output.crtc_id);
536 }
537
538#if QT_CONFIG(drm_atomic)
539 if (hasAtomicSupport() && !output.eglfs_plane) {
540 qCDebug(qLcKmsDebug, "No plane associated with output %s (crtc id %u) and atomic modesetting is enabled. This is bad.",
541 connectorName.constData(), output.crtc_id);
542 }
543#endif
544
545 m_crtc_allocator |= (1u << output.crtc_index);
546
547 vinfo.output = output;
548 return true;
549}
550
551drmModePropertyPtr QKmsDevice::connectorProperty(drmModeConnectorPtr connector, const QByteArray &name)
552{
553 drmModePropertyPtr prop;
554
555 for (int i = 0; i < connector->count_props; i++) {
556 prop = drmModeGetProperty(m_dri_fd, connector->props[i]);
557 if (!prop)
558 continue;
559 if (strcmp(prop->name, name.constData()) == 0)
560 return prop;
561 drmModeFreeProperty(prop);
562 }
563
564 return nullptr;
565}
566
567drmModePropertyBlobPtr QKmsDevice::connectorPropertyBlob(drmModeConnectorPtr connector, const QByteArray &name)
568{
569 drmModePropertyPtr prop;
570 drmModePropertyBlobPtr blob = nullptr;
571
572 for (int i = 0; i < connector->count_props && !blob; i++) {
573 prop = drmModeGetProperty(m_dri_fd, connector->props[i]);
574 if (!prop)
575 continue;
576 if ((prop->flags & DRM_MODE_PROP_BLOB) && (strcmp(prop->name, name.constData()) == 0))
577 blob = drmModeGetPropertyBlob(m_dri_fd, connector->prop_values[i]);
578 drmModeFreeProperty(prop);
579 }
580
581 return blob;
582}
583
584QKmsDevice::QKmsDevice(QKmsScreenConfig *screenConfig, const QString &path)
585 : m_screenConfig(screenConfig)
586 , m_path(path)
587 , m_dri_fd(-1)
588 , m_has_atomic_support(false)
590{
591 if (m_path.isEmpty()) {
592 m_path = m_screenConfig->devicePath();
593 qCDebug(qLcKmsDebug, "Using DRM device %s specified in config file", qPrintable(m_path));
594 if (m_path.isEmpty())
595 qFatal("No DRM device given");
596 } else {
597 qCDebug(qLcKmsDebug, "Using backend-provided DRM device %s", qPrintable(m_path));
598 }
599}
600
602{
603#if QT_CONFIG(drm_atomic)
604 threadLocalAtomicReset();
605#endif
606}
607
609{
611 return;
612
613 drmModeResPtr resources = drmModeGetResources(m_dri_fd);
614 if (!resources) {
615 qErrnoWarning(errno, "drmModeGetResources failed");
616 return;
617 }
618
619 QList<uint32_t> newConnects;
620 QList<uint32_t> newDisconnects;
621 const QMap<QString, QVariantMap> userConfig = m_screenConfig->outputSettings();
622
623 for (int i = 0; i < resources->count_connectors; i++) {
624 drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, resources->connectors[i]);
625 if (!connector) {
626 qErrnoWarning(errno, "drmModeGetConnector failed");
627 continue;
628 }
629
630 const uint32_t id = connector->connector_id;
631
632 const QByteArray connectorName = nameForConnector(connector);
633 const QVariantMap userCConfig = userConfig.value(QString::fromUtf8(connectorName));
634 const QByteArray mode = userCConfig.value(QStringLiteral("mode")).toByteArray().toLower();
635 if (mode == "off" || mode == "skip")
636 continue;
637
638 if (connector->connection == DRM_MODE_CONNECTED) {
639 if (!m_registeredScreens.contains(id))
640 newConnects.append(id);
641 else
642 qCDebug(qLcKmsDebug) << "Connected screen already registered: connector id=" << id;
643 }
644
645 if (connector->connection == DRM_MODE_DISCONNECTED) {
646 if (m_registeredScreens.contains(id))
647 newDisconnects.append(id);
648 else
649 qCDebug(qLcKmsDebug) << "Disconnected screen not registered: connector id=" << id;
650 }
651
652 drmModeFreeConnector(connector);
653 }
654
655 if (newConnects.isEmpty() && newDisconnects.isEmpty()) {
656 qCDebug(qLcKmsDebug) << "EGLFS/KMS: KMS-device-change but no new connects or disconnects "
657 << "to process - exiting";
658 return;
659 } else {
660 qCDebug(qLcKmsDebug) << "EGLFS/KMS: KMS-device-change, new connects:" << newConnects
661 << ", and disconnected: " << newDisconnects;
662 }
663
664 const int remainingScreenCount = m_registeredScreens.count() - newDisconnects.count();
665 if (remainingScreenCount == 0 && m_headlessScreen == nullptr) {
666 qCDebug(qLcKmsDebug) << "EGLFS/KMS: creating headless screen before"
667 << "unregistering screens to avoid having no screens";
668 m_headlessScreen = createHeadlessScreen();
669 registerScreen(m_headlessScreen, true, QPoint(),
670 QList<QPlatformScreen *>() << m_headlessScreen);
671 }
672
673 for (uint32_t connectorId : std::as_const(newDisconnects)) {
674 OrderedScreen orderedScreen = m_registeredScreens.take(connectorId);
675 QPlatformScreen *screen = orderedScreen.screen;
676
677 // Clear active crtc of the plane associated with the screen output
678 // and, if applicable, disassociate it from the eglfs plane.
679 uint32_t crtcId = (orderedScreen.vinfo.output.eglfs_plane != nullptr) // if we have an assigned plan
680 ? orderedScreen.vinfo.output.eglfs_plane->activeCrtcId // we use the active crtc_id to disable everything
681 : orderedScreen.vinfo.output.crtc_id; // if not, we use the default crtc_id
682
683 if (orderedScreen.vinfo.output.eglfs_plane != nullptr)
684 orderedScreen.vinfo.output.eglfs_plane->activeCrtcId = 0;
685
686 // Clear crtc allocator bit for screen
687 const int crtcIdx = orderedScreen.vinfo.output.crtc_index;
688 m_crtc_allocator &= ~(1u << crtcIdx);
689
690 const int ret = drmModeSetCrtc(m_dri_fd, crtcId, 0, 0, 0, nullptr, 0, nullptr);
691
692 if (ret != 0) {
693 qCWarning(qLcKmsDebug) << "Could not disable CRTC" << crtcId
694 << "on connector" << connectorId << "removal:" << ret;
695 } else {
696 qCDebug(qLcKmsDebug) << "Disabled CRTC" << crtcId
697 << "for connector " << connectorId << "disconnected";
698 }
699
700 // As we've already turned the crtc off, we don't want to restore the saved_crtc
701 if (orderedScreen.vinfo.output.saved_crtc) {
702 drmModeFreeCrtc(orderedScreen.vinfo.output.saved_crtc);
703 orderedScreen.vinfo.output.saved_crtc = nullptr;
704 updateScreenOutput(orderedScreen.screen, orderedScreen.vinfo.output);
705 }
706
707 unregisterScreen(screen);
708 }
709
710 for (uint32_t connectorId : std::as_const(newConnects)) {
711 drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, connectorId);
712 if (!connector) {
713 qErrnoWarning(errno, "drmModeGetConnector failed");
714 continue;
715 }
716
717 ScreenInfo vinfo;
718 bool succ = createScreenInfoForConnector(resources, connector, vinfo);
719 drmModeFreeConnector(connector);
720 if (!succ)
721 continue;
722
723 QPlatformScreen *screen = createScreen(vinfo.output);
724 if (!screen)
725 continue;
726
727 OrderedScreen orderedScreen(screen, vinfo);
728 m_registeredScreens[connectorId] = orderedScreen;
729 }
730
731 drmModeFreeResources(resources);
732
733 registerScreens(newConnects);
734}
735
737{
739 return;
740
741 drmModeResPtr resources = drmModeGetResources(m_dri_fd);
742 if (!resources) {
743 qErrnoWarning(errno, "drmModeGetResources failed");
744 return;
745 }
746
747 QList<uint32_t> newConnects;
748 QList<OrderedScreen> newDisconnects;
749
750 for (int i = 0; i < resources->count_connectors; i++) {
751 drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, resources->connectors[i]);
752 if (!connector)
753 continue;
754
755 if (m_registeredScreens.contains(connector->connector_id)) {
756 OrderedScreen &os = m_registeredScreens[connector->connector_id];
757
758 // As we're currently *re*creating the information of an used connector,
759 // we have to "fake" it being not in use at two places:
760 // (note: the only thing we'll restore is, in case of failure, the eglfs_plane
761 // probably not necessary but good practice)
762
763 // 1) crtc_allocator for the crtc
764 const int crtcIdx = os.vinfo.output.crtc_index;
765 m_crtc_allocator &= ~(1u << crtcIdx);
766
767 // 2) the plane itself
768 if (os.vinfo.output.eglfs_plane)
769 os.vinfo.output.eglfs_plane->activeCrtcId = 0;
770
771 // We also save the saved crtc to restore it in case of success
772 // (otherwise QKmsOutput::restoreMode would restore to a second-latest crtc,
773 // rather then the original one)
774 drmModeCrtcPtr saved_saved_crtc = nullptr;
775 if (os.vinfo.output.saved_crtc)
776 saved_saved_crtc = os.vinfo.output.saved_crtc;
777
778 ScreenInfo vinfo;
779 bool succ = createScreenInfoForConnector(resources, connector, vinfo);
780 if (!succ) {
781 // Here we either failed the recreate, or the config turns the screen off.
782 // In either case, we'll treat it as a disconnect
783
784 // Either this connector is disconnected, broken or turned off
785 // In all those cases we don't need or want to restore the previous mode
786 if (os.vinfo.output.saved_crtc) {
787 drmModeFreeCrtc(os.vinfo.output.saved_crtc);
788 os.vinfo.output.saved_crtc = nullptr;
789 updateScreenOutput(os.screen, os.vinfo.output);
790 }
791
792 // move from one container to another - we don't want registerScreens
793 // to deal with this, but need to call registerScreens before the disconnects
794 newDisconnects.append(m_registeredScreens.take(connector->connector_id));
795 drmModeFreeConnector(connector);
796 continue;
797 }
798 drmModeFreeConnector(connector);
799
800 drmModeFreeCrtc(vinfo.output.saved_crtc);
801 vinfo.output.saved_crtc = saved_saved_crtc; // This is vital as config changes should
802 // never override the original saved_crtc
803 os.vinfo = vinfo;
804 updateScreenOutput(os.screen, os.vinfo.output);
805
806 } else {
807 ScreenInfo vinfo;
808 bool succ = createScreenInfoForConnector(resources, connector, vinfo);
809 if (!succ) // If we fail here we do nothing, as there is nothing to restore or cleanup
810 continue;
811
812 QPlatformScreen *screen = createScreen(vinfo.output);
813 OrderedScreen orderedScreen(screen, vinfo);
814 m_registeredScreens[connector->connector_id] = orderedScreen;
815 newConnects.append(connector->connector_id);
816 }
817 }
818
819 // In case we end up with zero screen, we do the fallback first
820 if (m_registeredScreens.count() == 0 && m_headlessScreen == nullptr) {
821 // Create headless screen before unregistering screens to avoid having no screens
822 m_headlessScreen = createHeadlessScreen();
823 registerScreen(m_headlessScreen, true, QPoint(),
824 QList<QPlatformScreen *>() << m_headlessScreen);
825 }
826
827 // Register new and updates existing screens
828 registerScreens(newConnects);
829
830 // Last we unregister the disconncted ones
831 for (const OrderedScreen &os : newDisconnects)
832 unregisterScreen(os.screen);
833
834 drmModeFreeResources(resources);
835}
836
838{
839 // Headless mode using a render node: cannot do any output related DRM
840 // stuff. Skip it all and register a dummy screen.
842 QPlatformScreen *screen = createHeadlessScreen();
843 if (screen) {
844 qCDebug(qLcKmsDebug, "Headless mode enabled");
845 registerScreen(screen, true, QPoint(0, 0),
846 QList<QPlatformScreen *>() << screen);
847 return;
848 } else {
849 qWarning("QKmsDevice: Requested headless mode without support in the backend. Request is ignored.");
850 }
851 }
852
853 drmSetClientCap(m_dri_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
854
855#if QT_CONFIG(drm_atomic)
856 // check atomic support
857 m_has_atomic_support = !drmSetClientCap(m_dri_fd, DRM_CLIENT_CAP_ATOMIC, 1);
858 if (m_has_atomic_support) {
859 qCDebug(qLcKmsDebug, "Atomic reported as supported");
860 if (qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_ATOMIC")) {
861 qCDebug(qLcKmsDebug, "Atomic enabled");
862 } else {
863 qCDebug(qLcKmsDebug, "Atomic disabled");
864 m_has_atomic_support = false;
865 }
866 }
867#endif
868
869 drmModeResPtr resources = drmModeGetResources(m_dri_fd);
870 if (!resources) {
871 qErrnoWarning(errno, "drmModeGetResources failed");
872 return;
873 }
874
876
877 QList<uint32_t> newConnects;
878 int wantedConnectorIndex = -1;
879 bool ok;
880 int idx = qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_CONNECTOR_INDEX", &ok);
881 if (ok) {
882 if (idx >= 0 && idx < resources->count_connectors)
883 wantedConnectorIndex = idx;
884 else
885 qWarning("Invalid connector index %d, must be between 0 and %u", idx, resources->count_connectors - 1);
886 }
887
888 for (int i = 0; i < resources->count_connectors; i++) {
889 if (wantedConnectorIndex >= 0 && i != wantedConnectorIndex)
890 continue;
891
892 drmModeConnectorPtr connector = drmModeGetConnector(m_dri_fd, resources->connectors[i]);
893 if (!connector) {
894 qErrnoWarning(errno, "drmModeGetConnector failed");
895 continue;
896 }
897
898 ScreenInfo vinfo;
899 bool succ = createScreenInfoForConnector(resources, connector, vinfo);
900 uint32_t connectorId = connector->connector_id;
901 drmModeFreeConnector(connector);
902 if (!succ)
903 continue;
904
905 QPlatformScreen *screen = createScreen(vinfo.output);
906 if (!screen)
907 continue;
908
909 OrderedScreen orderedScreen(screen, vinfo);
910 m_registeredScreens[connectorId] = orderedScreen;
911 newConnects.append(connectorId);
912 }
913
914 drmModeFreeResources(resources);
915
916 if (!qEnvironmentVariable("QT_QPA_EGLFS_HOTPLUG_ENABLED").isEmpty()
917 && newConnects.empty() && m_headlessScreen == nullptr) {
918 qCDebug(qLcKmsDebug) << "'QT_QPA_EGLFS_HOTPLUG_ENABLED' was set and no screen was connected/found during start-up."
919 << "In order for Qt to operate properly a qt_headless screen will be created."
920 << "It will be automatically removed as soon as the first screen is connected";
921 // Create headless screen before unregistering screens to avoid having no screens
922 m_headlessScreen = createHeadlessScreen();
923 registerScreen(m_headlessScreen, true, QPoint(),
924 QList<QPlatformScreen *>() << m_headlessScreen);
925 }
926
927 registerScreens(newConnects);
928}
929
930void QKmsDevice::registerScreens(QList<uint32_t> newConnects)
931{
932 QList<OrderedScreen> screens = m_registeredScreens.values();
933
934 // Use stable sort to preserve the original (DRM connector) order
935 // for outputs with unspecified indices.
936 std::stable_sort(screens.begin(), screens.end(), orderedScreenLessThan);
937 qCDebug(qLcKmsDebug) << "Sorted screen list:" << screens;
938
939 // The final list of screens is available, so do the second phase setup.
940 // Hook up clone sources and targets.
941 for (const OrderedScreen &orderedScreen : std::as_const(screens)) {
942 QList<QPlatformScreen *> screensCloningThisScreen;
943 for (const OrderedScreen &s : std::as_const(screens)) {
944 if (s.vinfo.output.clone_source == orderedScreen.vinfo.output.name)
945 screensCloningThisScreen.append(s.screen);
946 }
947 QPlatformScreen *screenThisScreenClones = nullptr;
948 if (!orderedScreen.vinfo.output.clone_source.isEmpty()) {
949 for (const OrderedScreen &s : std::as_const(screens)) {
950 if (s.vinfo.output.name == orderedScreen.vinfo.output.clone_source) {
951 screenThisScreenClones = s.screen;
952 break;
953 }
954 }
955 }
956 if (screenThisScreenClones)
957 qCDebug(qLcKmsDebug) << orderedScreen.screen->name() << "clones" << screenThisScreenClones;
958 if (!screensCloningThisScreen.isEmpty())
959 qCDebug(qLcKmsDebug) << orderedScreen.screen->name() << "is cloned by" << screensCloningThisScreen;
960
961 registerScreenCloning(orderedScreen.screen, screenThisScreenClones, screensCloningThisScreen);
962 }
963
964 // Figure out the virtual desktop and register the screens to QPA/QGuiApplication.
965 QPoint pos(0, 0);
966 QList<OrderedScreen> siblings;
967 QList<QPoint> virtualPositions;
968 int primarySiblingIdx = -1;
969 QRegion deskRegion;
970
971 for (const OrderedScreen &orderedScreen : std::as_const(screens)) {
972 QPlatformScreen *s = orderedScreen.screen;
973 QPoint virtualPos(0, 0);
974 // set up a horizontal or vertical virtual desktop
975 if (orderedScreen.vinfo.virtualPos.x() == -1 || orderedScreen.vinfo.virtualPos.y() == -1) {
976 if (orderedScreen.vinfo.output.clone_source.isEmpty()) {
977 virtualPos = pos;
978 if (m_screenConfig->virtualDesktopLayout() == QKmsScreenConfig::VirtualDesktopLayoutVertical)
979 pos.ry() += s->geometry().height();
980 else
981 pos.rx() += s->geometry().width();
982 } else {
983 for (int i = 0; i < screens.count(); i++) {
984 const OrderedScreen &os = screens[i];
985 if (os.vinfo.output.name == orderedScreen.vinfo.output.clone_source) {
986 if (i >= virtualPositions.count()) {
987 qCWarning(qLcKmsDebug)
988 << "WARNING: When using clone on kms config,"
989 << "you have to either order your screens (virtualIndex),"
990 << "so clones come after their source,"
991 << "or specify 'virtualPos' for each clone."
992 << "Otherwise desktop-geomerty might not work properly!";
993 virtualPos = pos;
994 } else {
995 virtualPos = virtualPositions[i];
996 }
997 break;
998 }
999 }
1000 }
1001 } else {
1002 virtualPos = orderedScreen.vinfo.virtualPos;
1003 }
1004
1005 // The order in qguiapp's screens list will match the order set by
1006 // virtualIndex. This is not only handy but also required since for instance
1007 // evdevtouch relies on it when performing touch device - screen mapping.
1008 if (!m_screenConfig->separateScreens()) {
1009 siblings.append(orderedScreen);
1010 virtualPositions.append(virtualPos);
1011 if (orderedScreen.vinfo.isPrimary)
1012 primarySiblingIdx = siblings.size() - 1;
1013 } else {
1014 const bool isNewScreen = newConnects.contains(orderedScreen.vinfo.output.connector_id);
1015 if (isNewScreen) {
1016 qCDebug(qLcKmsDebug) << "Adding QPlatformScreen" << s << "(" << s->name() << ")"
1017 << "to QPA with geometry" << s->geometry()
1018 << ", virtual position" << virtualPos
1019 << "and isPrimary=" << orderedScreen.vinfo.isPrimary;
1020 registerScreen(s, orderedScreen.vinfo.isPrimary, virtualPos,
1021 QList<QPlatformScreen *>() << s);
1022 deskRegion += s->geometry();
1023 } else {
1024 qCDebug(qLcKmsDebug) << "Updating QPlatformScreen" << s << "(" << s->name() << ")"
1025 << "to QPA with geometry" << s->geometry()
1026 << ", virtual position" << virtualPos
1027 << "and isPrimary=" << orderedScreen.vinfo.isPrimary;
1028 updateScreen(s, virtualPos, QList<QPlatformScreen *>() << s);
1029 deskRegion += s->geometry();
1030 }
1031 }
1032 }
1033
1035 QList<QPlatformScreen *> platformScreenSiblings;
1036 for (int i = 0; i < siblings.count(); ++i) {
1037 platformScreenSiblings.append(siblings[i].screen);
1038 }
1039
1040 // enable the virtual desktop
1041 for (int i = 0; i < siblings.count(); ++i) {
1042 QPlatformScreen *screen = platformScreenSiblings[i];
1043 const OrderedScreen &orderedScreen = siblings[i];
1044 const bool isNewScreen = newConnects.contains(orderedScreen.vinfo.output.connector_id);
1045 if (isNewScreen) {
1046 qCDebug(qLcKmsDebug) << "Adding QPlatformScreen" << screen
1047 << "(" << screen->name() << ")"
1048 << "to QPA with geometry" << screen->geometry()
1049 << ", virtual position" << virtualPositions[i]
1050 << "and isPrimary=" << orderedScreen.vinfo.isPrimary;
1051 registerScreen(screen, i == primarySiblingIdx, virtualPositions[i],
1052 platformScreenSiblings);
1053 deskRegion += screen->geometry();
1054 } else {
1055 qCDebug(qLcKmsDebug) << "Updating QPlatformScreen" << screen
1056 << "(" << screen->name() << ")"
1057 << "to QPA with geometry" << screen->geometry()
1058 << ", virtual position" << virtualPositions[i]
1059 << "and isPrimary=" << orderedScreen.vinfo.isPrimary;
1060 updateScreen(screen, virtualPositions[i], platformScreenSiblings);
1061 deskRegion += screen->geometry();
1062 }
1063 }
1064 }
1065
1066 // Remove headless screen if other screens have become available
1067 if (!m_registeredScreens.empty() && m_headlessScreen) {
1068 unregisterScreen(m_headlessScreen);
1069 m_headlessScreen = nullptr;
1070 }
1071
1072 // Due to layout changes it's possible that we have to reset/bound
1073 // the cursor into the available space (otherwise the cursor might vanish)
1074 QPoint currCPos = QCursor::pos();
1075 if (!deskRegion.contains(currCPos)) {
1076
1077 // We try boudingRect first
1078 QRect deskRect = deskRegion.boundingRect();
1079 currCPos.setX(qMin(currCPos.x(), deskRect.width()) - 1);
1080 currCPos.setY(qMin(currCPos.y(), deskRect.height()) - 1);
1081
1082 // If boudingRect isn't good enough, we go to 0
1083 if (!deskRegion.contains(currCPos))
1084 currCPos = QPoint(0,0);
1085
1086 qCDebug(qLcKmsDebug) << "Due to desktop layout change, overriding cursor pos."
1087 << "Is: " << QCursor::pos() << ", will be: " << currCPos;
1088 QCursor::setPos(currCPos);
1089 }
1090}
1091
1093{
1094 // headless mode not supported by default
1095 return nullptr;
1096}
1097
1098// not all subclasses support screen cloning
1099void QKmsDevice::registerScreenCloning(QPlatformScreen *screen,
1100 QPlatformScreen *screenThisScreenClones,
1101 const QList<QPlatformScreen *> &screensCloningThisScreen)
1102{
1103 Q_UNUSED(screen);
1104 Q_UNUSED(screenThisScreenClones);
1105 Q_UNUSED(screensCloningThisScreen);
1106}
1107
1108void QKmsDevice::unregisterScreen(QPlatformScreen *screen)
1109{
1110 Q_UNUSED(screen);
1111}
1112
1113void QKmsDevice::updateScreen(QPlatformScreen *screen, const QPoint &virtualPos,
1114 const QList<QPlatformScreen *> &virtualSiblings)
1115{
1116 Q_UNUSED(screen);
1117 Q_UNUSED(virtualPos);
1118 Q_UNUSED(virtualSiblings);
1119}
1120
1121void QKmsDevice::updateScreenOutput(QPlatformScreen *screen, const QKmsOutput &output)
1122{
1123 Q_UNUSED(screen);
1124 Q_UNUSED(output);
1125}
1126
1127// drm_property_type_is is not available in old headers
1128static inline bool propTypeIs(drmModePropertyPtr prop, uint32_t type)
1129{
1130 if (prop->flags & DRM_MODE_PROP_EXTENDED_TYPE)
1131 return (prop->flags & DRM_MODE_PROP_EXTENDED_TYPE) == type;
1132 return prop->flags & type;
1133}
1134
1135void QKmsDevice::enumerateProperties(drmModeObjectPropertiesPtr objProps, PropCallback callback)
1136{
1137 for (uint32_t propIdx = 0; propIdx < objProps->count_props; ++propIdx) {
1138 drmModePropertyPtr prop = drmModeGetProperty(m_dri_fd, objProps->props[propIdx]);
1139 if (!prop)
1140 continue;
1141
1142 const quint64 value = objProps->prop_values[propIdx];
1143 qCDebug(qLcKmsDebug, " property %d: id = %u name = '%s'", propIdx, prop->prop_id, prop->name);
1144
1146 qCDebug(qLcKmsDebug, " type is SIGNED_RANGE, value is %lld, possible values are:", qint64(value));
1147 for (int i = 0; i < prop->count_values; ++i)
1148 qCDebug(qLcKmsDebug, " %lld", qint64(prop->values[i]));
1149 } else if (propTypeIs(prop, DRM_MODE_PROP_RANGE)) {
1150 qCDebug(qLcKmsDebug, " type is RANGE, value is %llu, possible values are:", value);
1151 for (int i = 0; i < prop->count_values; ++i)
1152 qCDebug(qLcKmsDebug, " %llu", quint64(prop->values[i]));
1153 } else if (propTypeIs(prop, DRM_MODE_PROP_ENUM)) {
1154 qCDebug(qLcKmsDebug, " type is ENUM, value is %llu, possible values are:", value);
1155 for (int i = 0; i < prop->count_enums; ++i)
1156 qCDebug(qLcKmsDebug, " enum %d: %s - %llu", i, prop->enums[i].name, quint64(prop->enums[i].value));
1157 } else if (propTypeIs(prop, DRM_MODE_PROP_BITMASK)) {
1158 qCDebug(qLcKmsDebug, " type is BITMASK, value is %llu, possible bits are:", value);
1159 for (int i = 0; i < prop->count_enums; ++i)
1160 qCDebug(qLcKmsDebug, " bitmask %d: %s - %u", i, prop->enums[i].name, 1 << prop->enums[i].value);
1161 } else if (propTypeIs(prop, DRM_MODE_PROP_BLOB)) {
1162 qCDebug(qLcKmsDebug, " type is BLOB");
1163 } else if (propTypeIs(prop, DRM_MODE_PROP_OBJECT)) {
1164 qCDebug(qLcKmsDebug, " type is OBJECT");
1165 }
1166
1167 callback(prop, value);
1168
1169 drmModeFreeProperty(prop);
1170 }
1171}
1172
1174{
1175 m_planes.clear();
1176
1177 drmModePlaneResPtr planeResources = drmModeGetPlaneResources(m_dri_fd);
1178 if (!planeResources)
1179 return;
1180
1181 const int countPlanes = planeResources->count_planes;
1182 qCDebug(qLcKmsDebug, "Found %d planes", countPlanes);
1183 for (int planeIdx = 0; planeIdx < countPlanes; ++planeIdx) {
1184 drmModePlanePtr drmplane = drmModeGetPlane(m_dri_fd, planeResources->planes[planeIdx]);
1185 if (!drmplane) {
1186 qCDebug(qLcKmsDebug, "Failed to query plane %d, ignoring", planeIdx);
1187 continue;
1188 }
1189
1190 QKmsPlane plane;
1191 plane.id = drmplane->plane_id;
1192 plane.possibleCrtcs = drmplane->possible_crtcs;
1193
1194 const int countFormats = drmplane->count_formats;
1195 QString formatStr;
1196 for (int i = 0; i < countFormats; ++i) {
1197 uint32_t f = drmplane->formats[i];
1198 plane.supportedFormats.append(f);
1199 formatStr += QString::asprintf("%c%c%c%c ", f, f >> 8, f >> 16, f >> 24);
1200 }
1201
1202 qCDebug(qLcKmsDebug, "plane %d: id = %u countFormats = %d possibleCrtcs = 0x%x supported formats = %s",
1203 planeIdx, plane.id, countFormats, plane.possibleCrtcs, qPrintable(formatStr));
1204
1205 drmModeFreePlane(drmplane);
1206
1207 drmModeObjectPropertiesPtr objProps = drmModeObjectGetProperties(m_dri_fd, plane.id, DRM_MODE_OBJECT_PLANE);
1208 if (!objProps) {
1209 qCDebug(qLcKmsDebug, "Failed to query plane %d object properties, ignoring", planeIdx);
1210 continue;
1211 }
1212
1213 enumerateProperties(objProps, [&plane](drmModePropertyPtr prop, quint64 value) {
1214 if (!strcmp(prop->name, "type")) {
1215 plane.type = QKmsPlane::Type(value);
1216 } else if (!strcmp(prop->name, "rotation")) {
1217 plane.initialRotation = QKmsPlane::Rotations(int(value));
1218 plane.availableRotations = { };
1219 if (propTypeIs(prop, DRM_MODE_PROP_BITMASK)) {
1220 for (int i = 0; i < prop->count_enums; ++i)
1221 plane.availableRotations |= QKmsPlane::Rotation(1 << prop->enums[i].value);
1222 }
1223 plane.rotationPropertyId = prop->prop_id;
1224 } else if (!strcasecmp(prop->name, "crtc_id")) {
1225 plane.crtcPropertyId = prop->prop_id;
1226 } else if (!strcasecmp(prop->name, "fb_id")) {
1227 plane.framebufferPropertyId = prop->prop_id;
1228 } else if (!strcasecmp(prop->name, "src_w")) {
1229 plane.srcwidthPropertyId = prop->prop_id;
1230 } else if (!strcasecmp(prop->name, "src_h")) {
1231 plane.srcheightPropertyId = prop->prop_id;
1232 } else if (!strcasecmp(prop->name, "crtc_w")) {
1233 plane.crtcwidthPropertyId = prop->prop_id;
1234 } else if (!strcasecmp(prop->name, "crtc_h")) {
1235 plane.crtcheightPropertyId = prop->prop_id;
1236 } else if (!strcasecmp(prop->name, "src_x")) {
1237 plane.srcXPropertyId = prop->prop_id;
1238 } else if (!strcasecmp(prop->name, "src_y")) {
1239 plane.srcYPropertyId = prop->prop_id;
1240 } else if (!strcasecmp(prop->name, "crtc_x")) {
1241 plane.crtcXPropertyId = prop->prop_id;
1242 } else if (!strcasecmp(prop->name, "crtc_y")) {
1243 plane.crtcYPropertyId = prop->prop_id;
1244 } else if (!strcasecmp(prop->name, "zpos")) {
1245 plane.zposPropertyId = prop->prop_id;
1246 } else if (!strcasecmp(prop->name, "blend_op")) {
1247 plane.blendOpPropertyId = prop->prop_id;
1248 }
1249 });
1250
1251 m_planes.append(plane);
1252
1253 drmModeFreeObjectProperties(objProps);
1254 }
1255
1256 drmModeFreePlaneResources(planeResources);
1257}
1258
1259int QKmsDevice::fd() const
1260{
1261 return m_dri_fd;
1262}
1263
1265{
1266 return m_path;
1267}
1268
1269void QKmsDevice::setFd(int fd)
1270{
1271 m_dri_fd = fd;
1272}
1273
1274
1276{
1277 return m_has_atomic_support;
1278}
1279
1280#if QT_CONFIG(drm_atomic)
1281drmModeAtomicReq *QKmsDevice::threadLocalAtomicRequest()
1282{
1283 if (!m_has_atomic_support)
1284 return nullptr;
1285
1286 AtomicReqs &a(m_atomicReqs.localData());
1287 if (!a.request)
1288 a.request = drmModeAtomicAlloc();
1289
1290 return a.request;
1291}
1292
1293bool QKmsDevice::threadLocalAtomicCommit(void *user_data)
1294{
1295 if (!m_has_atomic_support)
1296 return false;
1297
1298 AtomicReqs &a(m_atomicReqs.localData());
1299 if (!a.request)
1300 return false;
1301
1302 int ret = drmModeAtomicCommit(m_dri_fd, a.request,
1303 DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_ALLOW_MODESET,
1304 user_data);
1305
1306 if (ret) {
1307 qWarning("Failed to commit atomic request (code=%d)", ret);
1308 return false;
1309 }
1310
1311 a.previous_request = a.request;
1312 a.request = nullptr;
1313
1314 return true;
1315}
1316
1317void QKmsDevice::threadLocalAtomicReset()
1318{
1319 if (!m_has_atomic_support)
1320 return;
1321
1322 AtomicReqs &a(m_atomicReqs.localData());
1323 if (a.previous_request) {
1324 drmModeAtomicFree(a.previous_request);
1325 a.previous_request = nullptr;
1326 }
1327}
1328#endif
1329
1330void QKmsDevice::parseConnectorProperties(uint32_t connectorId, QKmsOutput *output)
1331{
1332 drmModeObjectPropertiesPtr objProps = drmModeObjectGetProperties(m_dri_fd, connectorId, DRM_MODE_OBJECT_CONNECTOR);
1333 if (!objProps) {
1334 qCDebug(qLcKmsDebug, "Failed to query connector %d object properties", connectorId);
1335 return;
1336 }
1337
1338 enumerateProperties(objProps, [output](drmModePropertyPtr prop, quint64 value) {
1339 Q_UNUSED(value);
1340 if (!strcasecmp(prop->name, "crtc_id"))
1341 output->crtcIdPropertyId = prop->prop_id;
1342 });
1343
1344 drmModeFreeObjectProperties(objProps);
1345}
1346
1347void QKmsDevice::parseCrtcProperties(uint32_t crtcId, QKmsOutput *output)
1348{
1349 drmModeObjectPropertiesPtr objProps = drmModeObjectGetProperties(m_dri_fd, crtcId, DRM_MODE_OBJECT_CRTC);
1350 if (!objProps) {
1351 qCDebug(qLcKmsDebug, "Failed to query crtc %d object properties", crtcId);
1352 return;
1353 }
1354
1355 enumerateProperties(objProps, [output](drmModePropertyPtr prop, quint64 value) {
1356 Q_UNUSED(value);
1357 if (!strcasecmp(prop->name, "mode_id"))
1358 output->modeIdPropertyId = prop->prop_id;
1359 else if (!strcasecmp(prop->name, "active"))
1360 output->activePropertyId = prop->prop_id;
1361 });
1362
1363 drmModeFreeObjectProperties(objProps);
1364}
1365
1367{
1368 return m_screenConfig;
1369}
1370
1372 : m_headless(false)
1373 , m_hwCursor(true)
1374 , m_separateScreens(false)
1375 , m_pbuffers(false)
1377{
1378 if (qEnvironmentVariableIntValue("QT_QPA_KMS_DISABLE_HWCURSOR"))
1379 m_hwCursor = false;
1380}
1381
1383{
1384 m_outputSettings.clear();
1386}
1387
1389{
1390 QByteArray json = qgetenv("QT_QPA_EGLFS_KMS_CONFIG");
1391 if (json.isEmpty()) {
1392 json = qgetenv("QT_QPA_KMS_CONFIG");
1393 if (json.isEmpty())
1394 return;
1395 }
1396
1397 qCDebug(qLcKmsDebug) << "Loading KMS setup from" << json;
1398
1399 QFile file(QString::fromUtf8(json));
1400 if (!file.open(QFile::ReadOnly)) {
1401 qCWarning(qLcKmsDebug) << "Could not open config file"
1402 << json << "for reading";
1403 return;
1404 }
1405
1406 const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
1407 if (!doc.isObject()) {
1408 qCWarning(qLcKmsDebug) << "Invalid config file" << json
1409 << "- no top-level JSON object";
1410 return;
1411 }
1412
1413 const QJsonObject object = doc.object();
1414
1415 const QString headlessStr = object.value("headless"_L1).toString();
1416 const QByteArray headless = headlessStr.toUtf8();
1417 QSize headlessSize;
1418 if (sscanf(headless.constData(), "%dx%d", &headlessSize.rwidth(), &headlessSize.rheight()) == 2) {
1419 m_headless = true;
1420 m_headlessSize = headlessSize;
1421 } else {
1422 m_headless = false;
1423 }
1424
1425 const QString headlessSizeStr = object.value(QLatin1String("headlessSize")).toString();
1426 if (sscanf(headlessSizeStr.toUtf8().constData(), "%dx%d", &headlessSize.rwidth(),
1427 &headlessSize.rheight()) == 2)
1428 m_headlessSize = headlessSize;
1429
1430 m_hwCursor = object.value("hwcursor"_L1).toBool(m_hwCursor);
1431 m_pbuffers = object.value("pbuffers"_L1).toBool(m_pbuffers);
1432 m_devicePath = object.value("device"_L1).toString();
1433 m_separateScreens = object.value("separateScreens"_L1).toBool(m_separateScreens);
1434
1435 const auto vdOriString = object.value("virtualDesktopLayout"_L1).toStringView();
1436 if (!vdOriString.isEmpty()) {
1437 if (vdOriString == "horizontal"_L1)
1439 else if (vdOriString == "vertical"_L1)
1441 else
1442 qCWarning(qLcKmsDebug, "Unknown virtualDesktopOrientation value %ls",
1443 qUtf16Printable(vdOriString.toString()));
1444 }
1445
1446 const QJsonArray outputs = object.value("outputs"_L1).toArray();
1447 for (int i = 0; i < outputs.size(); i++) {
1448 const QVariantMap outputSettings = outputs.at(i).toObject().toVariantMap();
1449
1450 if (outputSettings.contains(QStringLiteral("name"))) {
1451 const QString name = outputSettings.value(QStringLiteral("name")).toString();
1452
1453 if (m_outputSettings.contains(name)) {
1454 qCDebug(qLcKmsDebug) << "Output" << name << "configured multiple times!";
1455 }
1456
1457 m_outputSettings.insert(name, outputSettings);
1458 }
1459 }
1460
1461 qCDebug(qLcKmsDebug) << "Requested configuration (some settings may be ignored):\n"
1462 << "\theadless:" << m_headless << "\n"
1463 << "\thwcursor:" << m_hwCursor << "\n"
1464 << "\tpbuffers:" << m_pbuffers << "\n"
1465 << "\tseparateScreens:" << m_separateScreens << "\n"
1466 << "\tvirtualDesktopLayout:" << m_virtualDesktopLayout << "\n"
1467 << "\toutputs:" << m_outputSettings;
1468}
1469
1471{
1472 if (mode_set && saved_crtc) {
1473 drmModeSetCrtc(device->fd(),
1474 saved_crtc->crtc_id,
1475 saved_crtc->buffer_id,
1476 0, 0,
1477 &connector_id, 1,
1478 &saved_crtc->mode);
1479 mode_set = false;
1480 }
1481}
1482
1484{
1485 if (dpms_prop) {
1486 drmModeFreeProperty(dpms_prop);
1487 dpms_prop = nullptr;
1488 }
1489
1490 if (edid_blob) {
1491 drmModeFreePropertyBlob(edid_blob);
1492 edid_blob = nullptr;
1493 }
1494
1495 restoreMode(device);
1496
1497 if (saved_crtc) {
1498 drmModeFreeCrtc(saved_crtc);
1499 saved_crtc = nullptr;
1500 }
1501}
1502
1504{
1505 switch (subpixel) {
1506 default:
1507 case DRM_MODE_SUBPIXEL_UNKNOWN:
1508 case DRM_MODE_SUBPIXEL_NONE:
1509 return QPlatformScreen::Subpixel_None;
1510 case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
1511 return QPlatformScreen::Subpixel_RGB;
1512 case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
1513 return QPlatformScreen::Subpixel_BGR;
1514 case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
1515 return QPlatformScreen::Subpixel_VRGB;
1516 case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
1517 return QPlatformScreen::Subpixel_VBGR;
1518 }
1519}
1520
1521void QKmsOutput::setPowerState(QKmsDevice *device, QPlatformScreen::PowerState state)
1522{
1523 if (dpms_prop)
1524 drmModeConnectorSetProperty(device->fd(), connector_id,
1525 dpms_prop->prop_id, (int) state);
1526}
1527
1528QT_END_NAMESPACE
void enumerateProperties(drmModeObjectPropertiesPtr objProps, PropCallback callback)
int fd() const
void discoverPlanes()
void checkConnectedScreens()
drmModePropertyPtr connectorProperty(drmModeConnectorPtr connector, const QByteArray &name)
virtual void unregisterScreen(QPlatformScreen *screen)
void parseCrtcProperties(uint32_t crtcId, QKmsOutput *output)
void setFd(int fd)
QKmsScreenConfig * screenConfig() const
virtual QPlatformScreen * createHeadlessScreen()
virtual ~QKmsDevice()
void updateScreens()
QKmsScreenConfig * m_screenConfig
void createScreens()
int crtcForConnector(drmModeResPtr resources, drmModeConnectorPtr connector)
std::function< void(drmModePropertyPtr, quint64)> PropCallback
void parseConnectorProperties(uint32_t connectorId, QKmsOutput *output)
QKmsDevice(QKmsScreenConfig *screenConfig, const QString &path=QString())
QString devicePath() const
virtual void updateScreen(QPlatformScreen *screen, const QPoint &virtualPos, const QList< QPlatformScreen * > &virtualSiblings)
bool m_has_atomic_support
drmModePropertyBlobPtr connectorPropertyBlob(drmModeConnectorPtr connector, const QByteArray &name)
bool createScreenInfoForConnector(drmModeResPtr resources, drmModeConnectorPtr connector, ScreenInfo &vinfo)
virtual void registerScreenCloning(QPlatformScreen *screen, QPlatformScreen *screenThisScreenClones, const QList< QPlatformScreen * > &screensCloningThisScreen)
virtual void updateScreenOutput(QPlatformScreen *screen, const QKmsOutput &output)
void registerScreens(QList< uint32_t > newConnects=QList< uint32_t >())
bool hasAtomicSupport()
bool headless() const
bool separateScreens() const
virtual void loadConfig()
VirtualDesktopLayout m_virtualDesktopLayout
QDebug operator<<(QDebug dbg, const NSObject *nsObject)
Definition qcore_mac.mm:209
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
static void assignPlane(QKmsOutput *output, QKmsPlane *plane)
static const char *const connector_type_names[]
QDebug operator<<(QDebug dbg, const QKmsDevice::OrderedScreen &s)
static bool orderedScreenLessThan(const QKmsDevice::OrderedScreen &a, const QKmsDevice::OrderedScreen &b)
static bool propTypeIs(drmModePropertyPtr prop, uint32_t type)
static QByteArray nameForConnector(const drmModeConnectorPtr connector)
static bool parseModeline(const QByteArray &text, drmModeModeInfoPtr mode)
OutputConfiguration
@ OutputConfigModeline
@ OutputConfigSkip
@ OutputConfigPreferred
@ OutputConfigMode
@ OutputConfigOff
@ OutputConfigCurrent
#define ARRAY_LENGTH(a)
#define DRM_CLIENT_CAP_UNIVERSAL_PLANES
#define DRM_MODE_PROP_SIGNED_RANGE
#define DRM_MODE_PROP_EXTENDED_TYPE
#define DRM_MODE_PROP_OBJECT
OrderedScreen(QPlatformScreen *screen, const ScreenInfo &vinfo)
void restoreMode(QKmsDevice *device)
uint32_t modeIdPropertyId
uint32_t forced_plane_id
struct QKmsPlane * eglfs_plane
drmModePropertyPtr dpms_prop
void setPowerState(QKmsDevice *device, QPlatformScreen::PowerState state)
uint32_t crtc_id
bool forced_plane_set
drmModeCrtcPtr saved_crtc
drmModePropertyBlobPtr edid_blob
QPlatformScreen::SubpixelAntialiasingType subpixelAntialiasingTypeHint() const
void cleanup(QKmsDevice *device)
uint32_t activePropertyId
bool drm_format_requested_by_user
uint32_t crtc_index
uint32_t crtcIdPropertyId
bool wants_forced_plane
uint32_t connector_id
uint32_t srcheightPropertyId
uint32_t zposPropertyId
uint32_t crtcXPropertyId
uint32_t activeCrtcId
uint32_t srcXPropertyId
uint32_t framebufferPropertyId
uint32_t crtcwidthPropertyId
uint32_t crtcPropertyId
uint32_t rotationPropertyId
uint32_t srcYPropertyId
uint32_t crtcheightPropertyId
uint32_t crtcYPropertyId
int possibleCrtcs
uint32_t srcwidthPropertyId
uint32_t blendOpPropertyId