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_LOGGING_CATEGORY(qLcKmsDebug, "qt.qpa.eglfs.kms")
23
24enum OutputConfiguration {
25 OutputConfigOff,
26 OutputConfigPreferred,
27 OutputConfigCurrent,
28 OutputConfigSkip,
29 OutputConfigMode,
30 OutputConfigModeline
31};
32
33int QKmsDevice::crtcForConnector(drmModeResPtr resources, drmModeConnectorPtr connector)
34{
35 int candidate = -1;
36
37 for (int i = 0; i < connector->count_encoders; i++) {
38 drmModeEncoderPtr encoder = drmModeGetEncoder(fd: m_dri_fd, encoder_id: connector->encoders[i]);
39 if (!encoder) {
40 qWarning(msg: "Failed to get encoder");
41 continue;
42 }
43
44 quint32 encoderId = encoder->encoder_id;
45 quint32 crtcId = encoder->crtc_id;
46 quint32 possibleCrtcs = encoder->possible_crtcs;
47 drmModeFreeEncoder(ptr: encoder);
48
49 for (int j = 0; j < resources->count_crtcs; j++) {
50 bool isPossible = possibleCrtcs & (1 << j);
51 bool isAvailable = !(m_crtc_allocator & (1 << j));
52 // Preserve the existing CRTC -> encoder -> connector routing if
53 // any. It makes the initialization faster, and may be better
54 // since we have a very dumb picking algorithm.
55 bool isBestChoice = (!connector->encoder_id ||
56 (connector->encoder_id == encoderId &&
57 resources->crtcs[j] == crtcId));
58
59 if (isPossible && isAvailable && isBestChoice) {
60 return j;
61 } else if (isPossible && isAvailable) {
62 candidate = j;
63 }
64 }
65 }
66
67 return candidate;
68}
69
70static const char * const connector_type_names[] = { // must match DRM_MODE_CONNECTOR_*
71 "None",
72 "VGA",
73 "DVI",
74 "DVI",
75 "DVI",
76 "Composite",
77 "TV",
78 "LVDS",
79 "CTV",
80 "DIN",
81 "DP",
82 "HDMI",
83 "HDMI",
84 "TV",
85 "eDP",
86 "Virtual",
87 "DSI"
88};
89
90static QByteArray nameForConnector(const drmModeConnectorPtr connector)
91{
92 QByteArray connectorName("UNKNOWN");
93
94 if (connector->connector_type < ARRAY_LENGTH(connector_type_names))
95 connectorName = connector_type_names[connector->connector_type];
96
97 connectorName += QByteArray::number(connector->connector_type_id);
98
99 return connectorName;
100}
101
102static bool parseModeline(const QByteArray &text, drmModeModeInfoPtr mode)
103{
104 char hsync[16];
105 char vsync[16];
106 float fclock;
107
108 mode->type = DRM_MODE_TYPE_USERDEF;
109 mode->hskew = 0;
110 mode->vscan = 0;
111 mode->vrefresh = 0;
112 mode->flags = 0;
113
114 if (sscanf(s: text.constData(), format: "%f %hd %hd %hd %hd %hd %hd %hd %hd %15s %15s",
115 &fclock,
116 &mode->hdisplay,
117 &mode->hsync_start,
118 &mode->hsync_end,
119 &mode->htotal,
120 &mode->vdisplay,
121 &mode->vsync_start,
122 &mode->vsync_end,
123 &mode->vtotal, hsync, vsync) != 11)
124 return false;
125
126 mode->clock = fclock * 1000;
127
128 if (strcmp(s1: hsync, s2: "+hsync") == 0)
129 mode->flags |= DRM_MODE_FLAG_PHSYNC;
130 else if (strcmp(s1: hsync, s2: "-hsync") == 0)
131 mode->flags |= DRM_MODE_FLAG_NHSYNC;
132 else
133 return false;
134
135 if (strcmp(s1: vsync, s2: "+vsync") == 0)
136 mode->flags |= DRM_MODE_FLAG_PVSYNC;
137 else if (strcmp(s1: vsync, s2: "-vsync") == 0)
138 mode->flags |= DRM_MODE_FLAG_NVSYNC;
139 else
140 return false;
141
142 return true;
143}
144
145static inline void assignPlane(QKmsOutput *output, QKmsPlane *plane)
146{
147 if (output->eglfs_plane)
148 output->eglfs_plane->activeCrtcId = 0;
149
150 plane->activeCrtcId = output->crtc_id;
151 output->eglfs_plane = plane;
152}
153
154QPlatformScreen *QKmsDevice::createScreenForConnector(drmModeResPtr resources,
155 drmModeConnectorPtr connector,
156 ScreenInfo *vinfo)
157{
158 Q_ASSERT(vinfo);
159 const QByteArray connectorName = nameForConnector(connector);
160
161 const int crtc = crtcForConnector(resources, connector);
162 if (crtc < 0) {
163 qWarning() << "No usable crtc/encoder pair for connector" << connectorName;
164 return nullptr;
165 }
166
167 OutputConfiguration configuration;
168 QSize configurationSize;
169 int configurationRefresh = 0;
170 drmModeModeInfo configurationModeline;
171
172 auto userConfig = m_screenConfig->outputSettings();
173 QVariantMap userConnectorConfig = userConfig.value(key: QString::fromUtf8(ba: connectorName));
174 // default to the preferred mode unless overridden in the config
175 const QByteArray mode = userConnectorConfig.value(QStringLiteral("mode"), QStringLiteral("preferred"))
176 .toByteArray().toLower();
177 if (mode == "off") {
178 configuration = OutputConfigOff;
179 } else if (mode == "preferred") {
180 configuration = OutputConfigPreferred;
181 } else if (mode == "current") {
182 configuration = OutputConfigCurrent;
183 } else if (mode == "skip") {
184 configuration = OutputConfigSkip;
185 } else if (sscanf(s: mode.constData(), format: "%dx%d@%d", &configurationSize.rwidth(), &configurationSize.rheight(),
186 &configurationRefresh) == 3)
187 {
188 configuration = OutputConfigMode;
189 } else if (sscanf(s: mode.constData(), format: "%dx%d", &configurationSize.rwidth(), &configurationSize.rheight()) == 2) {
190 configuration = OutputConfigMode;
191 } else if (parseModeline(text: mode, mode: &configurationModeline)) {
192 configuration = OutputConfigModeline;
193 } else {
194 qWarning(msg: "Invalid mode \"%s\" for output %s", mode.constData(), connectorName.constData());
195 configuration = OutputConfigPreferred;
196 }
197
198 *vinfo = ScreenInfo();
199 vinfo->virtualIndex = userConnectorConfig.value(QStringLiteral("virtualIndex"), INT_MAX).toInt();
200 if (userConnectorConfig.contains(QStringLiteral("virtualPos"))) {
201 const QByteArray vpos = userConnectorConfig.value(QStringLiteral("virtualPos")).toByteArray();
202 const QByteArrayList vposComp = vpos.split(sep: ',');
203 if (vposComp.size() == 2)
204 vinfo->virtualPos = QPoint(vposComp[0].trimmed().toInt(), vposComp[1].trimmed().toInt());
205 }
206 if (userConnectorConfig.value(QStringLiteral("primary")).toBool())
207 vinfo->isPrimary = true;
208
209 const uint32_t crtc_id = resources->crtcs[crtc];
210
211 if (configuration == OutputConfigOff) {
212 qCDebug(qLcKmsDebug) << "Turning off output" << connectorName;
213 drmModeSetCrtc(fd: m_dri_fd, crtcId: crtc_id, bufferId: 0, x: 0, y: 0, connectors: 0, count: 0, mode: nullptr);
214 return nullptr;
215 }
216
217 // Skip disconnected output
218 if (configuration == OutputConfigPreferred && connector->connection == DRM_MODE_DISCONNECTED) {
219 qCDebug(qLcKmsDebug) << "Skipping disconnected output" << connectorName;
220 return nullptr;
221 }
222
223 if (configuration == OutputConfigSkip) {
224 qCDebug(qLcKmsDebug) << "Skipping output" << connectorName;
225 return nullptr;
226 }
227
228 // Get the current mode on the current crtc
229 drmModeModeInfo crtc_mode;
230 memset(s: &crtc_mode, c: 0, n: sizeof crtc_mode);
231 if (drmModeEncoderPtr encoder = drmModeGetEncoder(fd: m_dri_fd, encoder_id: connector->encoder_id)) {
232 drmModeCrtcPtr crtc = drmModeGetCrtc(fd: m_dri_fd, crtcId: encoder->crtc_id);
233 drmModeFreeEncoder(ptr: encoder);
234
235 if (!crtc)
236 return nullptr;
237
238 if (crtc->mode_valid)
239 crtc_mode = crtc->mode;
240
241 drmModeFreeCrtc(ptr: crtc);
242 }
243
244 QList<drmModeModeInfo> modes;
245 modes.reserve(asize: connector->count_modes);
246 qCDebug(qLcKmsDebug) << connectorName << "mode count:" << connector->count_modes
247 << "crtc index:" << crtc << "crtc id:" << crtc_id;
248 for (int i = 0; i < connector->count_modes; i++) {
249 const drmModeModeInfo &mode = connector->modes[i];
250 qCDebug(qLcKmsDebug) << "mode" << i << mode.hdisplay << "x" << mode.vdisplay
251 << '@' << mode.vrefresh << "hz";
252 modes << connector->modes[i];
253 }
254
255 int preferred = -1;
256 int current = -1;
257 int configured = -1;
258 int best = -1;
259
260 for (int i = modes.size() - 1; i >= 0; i--) {
261 const drmModeModeInfo &m = modes.at(i);
262
263 if (configuration == OutputConfigMode
264 && m.hdisplay == configurationSize.width()
265 && m.vdisplay == configurationSize.height()
266 && (!configurationRefresh || m.vrefresh == uint32_t(configurationRefresh)))
267 {
268 configured = i;
269 }
270
271 if (!memcmp(s1: &crtc_mode, s2: &m, n: sizeof m))
272 current = i;
273
274 if (m.type & DRM_MODE_TYPE_PREFERRED)
275 preferred = i;
276
277 best = i;
278 }
279
280 if (configuration == OutputConfigModeline) {
281 modes << configurationModeline;
282 configured = modes.size() - 1;
283 }
284
285 if (current < 0 && crtc_mode.clock != 0) {
286 modes << crtc_mode;
287 current = modes.size() - 1;
288 }
289
290 if (configuration == OutputConfigCurrent)
291 configured = current;
292
293 int selected_mode = -1;
294
295 if (configured >= 0)
296 selected_mode = configured;
297 else if (preferred >= 0)
298 selected_mode = preferred;
299 else if (current >= 0)
300 selected_mode = current;
301 else if (best >= 0)
302 selected_mode = best;
303
304 if (selected_mode < 0) {
305 qWarning() << "No modes available for output" << connectorName;
306 return nullptr;
307 } else {
308 int width = modes[selected_mode].hdisplay;
309 int height = modes[selected_mode].vdisplay;
310 int refresh = modes[selected_mode].vrefresh;
311 qCDebug(qLcKmsDebug) << "Selected mode" << selected_mode << ":" << width << "x" << height
312 << '@' << refresh << "hz for output" << connectorName;
313 }
314
315 // physical size from connector < config values < env vars
316 int pwidth = qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_PHYSICAL_WIDTH");
317 if (!pwidth)
318 pwidth = qEnvironmentVariableIntValue(varName: "QT_QPA_PHYSICAL_WIDTH");
319 int pheight = qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_PHYSICAL_HEIGHT");
320 if (!pheight)
321 pheight = qEnvironmentVariableIntValue(varName: "QT_QPA_PHYSICAL_HEIGHT");
322 QSizeF physSize(pwidth, pheight);
323 if (physSize.isEmpty()) {
324 physSize = QSize(userConnectorConfig.value(QStringLiteral("physicalWidth")).toInt(),
325 userConnectorConfig.value(QStringLiteral("physicalHeight")).toInt());
326 if (physSize.isEmpty()) {
327 physSize.setWidth(connector->mmWidth);
328 physSize.setHeight(connector->mmHeight);
329 }
330 }
331 qCDebug(qLcKmsDebug) << "Physical size is" << physSize << "mm" << "for output" << connectorName;
332
333 const QByteArray formatStr = userConnectorConfig.value(QStringLiteral("format"), defaultValue: QString())
334 .toByteArray().toLower();
335 uint32_t drmFormat;
336 bool drmFormatExplicit = true;
337 if (formatStr.isEmpty()) {
338 drmFormat = DRM_FORMAT_XRGB8888;
339 drmFormatExplicit = false;
340 } else if (formatStr == "xrgb8888") {
341 drmFormat = DRM_FORMAT_XRGB8888;
342 } else if (formatStr == "xbgr8888") {
343 drmFormat = DRM_FORMAT_XBGR8888;
344 } else if (formatStr == "argb8888") {
345 drmFormat = DRM_FORMAT_ARGB8888;
346 } else if (formatStr == "abgr8888") {
347 drmFormat = DRM_FORMAT_ABGR8888;
348 } else if (formatStr == "rgb565") {
349 drmFormat = DRM_FORMAT_RGB565;
350 } else if (formatStr == "bgr565") {
351 drmFormat = DRM_FORMAT_BGR565;
352 } else if (formatStr == "xrgb2101010") {
353 drmFormat = DRM_FORMAT_XRGB2101010;
354 } else if (formatStr == "xbgr2101010") {
355 drmFormat = DRM_FORMAT_XBGR2101010;
356 } else if (formatStr == "argb2101010") {
357 drmFormat = DRM_FORMAT_ARGB2101010;
358 } else if (formatStr == "abgr2101010") {
359 drmFormat = DRM_FORMAT_ABGR2101010;
360 } else {
361 qWarning(msg: "Invalid pixel format \"%s\" for output %s", formatStr.constData(), connectorName.constData());
362 drmFormat = DRM_FORMAT_XRGB8888;
363 drmFormatExplicit = false;
364 }
365 qCDebug(qLcKmsDebug) << "Format is" << Qt::hex << drmFormat << Qt::dec << "requested_by_user =" << drmFormatExplicit
366 << "for output" << connectorName;
367
368 const QString cloneSource = userConnectorConfig.value(QStringLiteral("clones")).toString();
369 if (!cloneSource.isEmpty())
370 qCDebug(qLcKmsDebug) << "Output" << connectorName << " clones output " << cloneSource;
371
372 QSize framebufferSize;
373 bool framebufferSizeSet = false;
374 const QByteArray fbsize = userConnectorConfig.value(QStringLiteral("size")).toByteArray().toLower();
375 if (!fbsize.isEmpty()) {
376 if (sscanf(s: fbsize.constData(), format: "%dx%d", &framebufferSize.rwidth(), &framebufferSize.rheight()) == 2) {
377#if QT_CONFIG(drm_atomic)
378 if (hasAtomicSupport())
379 framebufferSizeSet = true;
380#endif
381 if (!framebufferSizeSet)
382 qWarning(msg: "Setting framebuffer size is only available with DRM atomic API");
383 } else {
384 qWarning(msg: "Invalid framebuffer size '%s'", fbsize.constData());
385 }
386 }
387 if (!framebufferSizeSet) {
388 framebufferSize.setWidth(modes[selected_mode].hdisplay);
389 framebufferSize.setHeight(modes[selected_mode].vdisplay);
390 }
391
392 qCDebug(qLcKmsDebug) << "Output" << connectorName << "framebuffer size is " << framebufferSize;
393
394 QKmsOutput output;
395 output.name = QString::fromUtf8(ba: connectorName);
396 output.connector_id = connector->connector_id;
397 output.crtc_index = crtc;
398 output.crtc_id = crtc_id;
399 output.physical_size = physSize;
400 output.preferred_mode = preferred >= 0 ? preferred : selected_mode;
401 output.mode = selected_mode;
402 output.mode_set = false;
403 output.saved_crtc = drmModeGetCrtc(fd: m_dri_fd, crtcId: crtc_id);
404 output.modes = modes;
405 output.subpixel = connector->subpixel;
406 output.dpms_prop = connectorProperty(connector, QByteArrayLiteral("DPMS"));
407 output.edid_blob = connectorPropertyBlob(connector, QByteArrayLiteral("EDID"));
408 output.wants_forced_plane = false;
409 output.forced_plane_id = 0;
410 output.forced_plane_set = false;
411 output.drm_format = drmFormat;
412 output.drm_format_requested_by_user = drmFormatExplicit;
413 output.clone_source = cloneSource;
414 output.size = framebufferSize;
415
416#if QT_CONFIG(drm_atomic)
417 if (drmModeCreatePropertyBlob(fd: m_dri_fd, data: &modes[selected_mode], size: sizeof(drmModeModeInfo),
418 id: &output.mode_blob_id) != 0) {
419 qCDebug(qLcKmsDebug) << "Failed to create mode blob for mode" << selected_mode;
420 }
421
422 parseConnectorProperties(connectorId: output.connector_id, output: &output);
423 parseCrtcProperties(crtcId: output.crtc_id, output: &output);
424#endif
425
426 QString planeListStr;
427 for (QKmsPlane &plane : m_planes) {
428 if (plane.possibleCrtcs & (1 << output.crtc_index)) {
429 output.available_planes.append(t: plane);
430 planeListStr.append(s: QString::number(plane.id));
431 planeListStr.append(c: u' ');
432
433 // Choose the first primary plane that is not already assigned to
434 // another screen's associated crtc.
435 if (!output.eglfs_plane && plane.type == QKmsPlane::PrimaryPlane && !plane.activeCrtcId)
436 assignPlane(output: &output, plane: &plane);
437 }
438 }
439 qCDebug(qLcKmsDebug, "Output %s can use %d planes: %s",
440 connectorName.constData(), int(output.available_planes.size()), qPrintable(planeListStr));
441
442 // This is for the EGLDevice/EGLStream backend. On some of those devices one
443 // may want to target a pre-configured plane. It is probably useless for
444 // eglfs_kms and others. Do not confuse with generic plane support (available_planes).
445 bool ok;
446 int idx = qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_KMS_PLANE_INDEX", ok: &ok);
447 if (ok) {
448 drmModePlaneRes *planeResources = drmModeGetPlaneResources(fd: m_dri_fd);
449 if (planeResources) {
450 if (idx >= 0 && idx < int(planeResources->count_planes)) {
451 drmModePlane *plane = drmModeGetPlane(fd: m_dri_fd, plane_id: planeResources->planes[idx]);
452 if (plane) {
453 output.wants_forced_plane = true;
454 output.forced_plane_id = plane->plane_id;
455 qCDebug(qLcKmsDebug, "Forcing plane index %d, plane id %u (belongs to crtc id %u)",
456 idx, plane->plane_id, plane->crtc_id);
457
458 for (QKmsPlane &kmsplane : m_planes) {
459 if (kmsplane.id == output.forced_plane_id) {
460 assignPlane(output: &output, plane: &kmsplane);
461 break;
462 }
463 }
464
465 drmModeFreePlane(ptr: plane);
466 }
467 } else {
468 qWarning(msg: "Invalid plane index %d, must be between 0 and %u", idx, planeResources->count_planes - 1);
469 }
470 }
471 }
472
473 // A more useful version: allows specifying "crtc_id,plane_id:crtc_id,plane_id:..."
474 // in order to allow overriding the plane used for a given crtc.
475 if (qEnvironmentVariableIsSet(varName: "QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS")) {
476 const QString val = qEnvironmentVariable(varName: "QT_QPA_EGLFS_KMS_PLANES_FOR_CRTCS");
477 qCDebug(qLcKmsDebug, "crtc_id:plane_id override list: %s", qPrintable(val));
478 const QStringList crtcPlanePairs = val.split(sep: u':');
479 for (const QString &crtcPlanePair : crtcPlanePairs) {
480 const QStringList values = crtcPlanePair.split(sep: u',');
481 if (values.size() == 2 && uint(values[0].toInt()) == output.crtc_id) {
482 uint planeId = values[1].toInt();
483 for (QKmsPlane &kmsplane : m_planes) {
484 if (kmsplane.id == planeId) {
485 assignPlane(output: &output, plane: &kmsplane);
486 break;
487 }
488 }
489 }
490 }
491 }
492
493 if (output.eglfs_plane) {
494 qCDebug(qLcKmsDebug, "Chose plane %u for output %s (crtc id %u) (may not be applicable)",
495 output.eglfs_plane->id, connectorName.constData(), output.crtc_id);
496 }
497
498#if QT_CONFIG(drm_atomic)
499 if (hasAtomicSupport() && !output.eglfs_plane) {
500 qCDebug(qLcKmsDebug, "No plane associated with output %s (crtc id %u) and atomic modesetting is enabled. This is bad.",
501 connectorName.constData(), output.crtc_id);
502 }
503#endif
504
505 m_crtc_allocator |= (1 << output.crtc_index);
506
507 vinfo->output = output;
508
509 return createScreen(output);
510}
511
512drmModePropertyPtr QKmsDevice::connectorProperty(drmModeConnectorPtr connector, const QByteArray &name)
513{
514 drmModePropertyPtr prop;
515
516 for (int i = 0; i < connector->count_props; i++) {
517 prop = drmModeGetProperty(fd: m_dri_fd, propertyId: connector->props[i]);
518 if (!prop)
519 continue;
520 if (strcmp(s1: prop->name, s2: name.constData()) == 0)
521 return prop;
522 drmModeFreeProperty(ptr: prop);
523 }
524
525 return nullptr;
526}
527
528drmModePropertyBlobPtr QKmsDevice::connectorPropertyBlob(drmModeConnectorPtr connector, const QByteArray &name)
529{
530 drmModePropertyPtr prop;
531 drmModePropertyBlobPtr blob = nullptr;
532
533 for (int i = 0; i < connector->count_props && !blob; i++) {
534 prop = drmModeGetProperty(fd: m_dri_fd, propertyId: connector->props[i]);
535 if (!prop)
536 continue;
537 if ((prop->flags & DRM_MODE_PROP_BLOB) && (strcmp(s1: prop->name, s2: name.constData()) == 0))
538 blob = drmModeGetPropertyBlob(fd: m_dri_fd, blob_id: connector->prop_values[i]);
539 drmModeFreeProperty(ptr: prop);
540 }
541
542 return blob;
543}
544
545QKmsDevice::QKmsDevice(QKmsScreenConfig *screenConfig, const QString &path)
546 : m_screenConfig(screenConfig)
547 , m_path(path)
548 , m_dri_fd(-1)
549 , m_has_atomic_support(false)
550 , m_crtc_allocator(0)
551{
552 if (m_path.isEmpty()) {
553 m_path = m_screenConfig->devicePath();
554 qCDebug(qLcKmsDebug, "Using DRM device %s specified in config file", qPrintable(m_path));
555 if (m_path.isEmpty())
556 qFatal(msg: "No DRM device given");
557 } else {
558 qCDebug(qLcKmsDebug, "Using backend-provided DRM device %s", qPrintable(m_path));
559 }
560}
561
562QKmsDevice::~QKmsDevice()
563{
564#if QT_CONFIG(drm_atomic)
565 threadLocalAtomicReset();
566#endif
567}
568
569struct OrderedScreen
570{
571 OrderedScreen() : screen(nullptr) { }
572 OrderedScreen(QPlatformScreen *screen, const QKmsDevice::ScreenInfo &vinfo)
573 : screen(screen), vinfo(vinfo) { }
574 QPlatformScreen *screen;
575 QKmsDevice::ScreenInfo vinfo;
576};
577
578QDebug operator<<(QDebug dbg, const OrderedScreen &s)
579{
580 QDebugStateSaver saver(dbg);
581 dbg.nospace() << "OrderedScreen(QPlatformScreen=" << s.screen << " (" << s.screen->name() << ") : "
582 << s.vinfo.virtualIndex
583 << " / " << s.vinfo.virtualPos
584 << " / primary: " << s.vinfo.isPrimary
585 << ")";
586 return dbg;
587}
588
589static bool orderedScreenLessThan(const OrderedScreen &a, const OrderedScreen &b)
590{
591 return a.vinfo.virtualIndex < b.vinfo.virtualIndex;
592}
593
594void QKmsDevice::createScreens()
595{
596 // Headless mode using a render node: cannot do any output related DRM
597 // stuff. Skip it all and register a dummy screen.
598 if (m_screenConfig->headless()) {
599 QPlatformScreen *screen = createHeadlessScreen();
600 if (screen) {
601 qCDebug(qLcKmsDebug, "Headless mode enabled");
602 registerScreen(screen, isPrimary: true, virtualPos: QPoint(0, 0), virtualSiblings: QList<QPlatformScreen *>());
603 return;
604 } else {
605 qWarning(msg: "QKmsDevice: Requested headless mode without support in the backend. Request is ignored.");
606 }
607 }
608
609 drmSetClientCap(fd: m_dri_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, value: 1);
610
611#if QT_CONFIG(drm_atomic)
612 // check atomic support
613 m_has_atomic_support = !drmSetClientCap(fd: m_dri_fd, DRM_CLIENT_CAP_ATOMIC, value: 1);
614 if (m_has_atomic_support) {
615 qCDebug(qLcKmsDebug, "Atomic reported as supported");
616 if (qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_KMS_ATOMIC")) {
617 qCDebug(qLcKmsDebug, "Atomic enabled");
618 } else {
619 qCDebug(qLcKmsDebug, "Atomic disabled");
620 m_has_atomic_support = false;
621 }
622 }
623#endif
624
625 drmModeResPtr resources = drmModeGetResources(fd: m_dri_fd);
626 if (!resources) {
627 qErrnoWarning(errno, msg: "drmModeGetResources failed");
628 return;
629 }
630
631 discoverPlanes();
632
633 QList<OrderedScreen> screens;
634
635 int wantedConnectorIndex = -1;
636 bool ok;
637 int idx = qEnvironmentVariableIntValue(varName: "QT_QPA_EGLFS_KMS_CONNECTOR_INDEX", ok: &ok);
638 if (ok) {
639 if (idx >= 0 && idx < resources->count_connectors)
640 wantedConnectorIndex = idx;
641 else
642 qWarning(msg: "Invalid connector index %d, must be between 0 and %u", idx, resources->count_connectors - 1);
643 }
644
645 for (int i = 0; i < resources->count_connectors; i++) {
646 if (wantedConnectorIndex >= 0 && i != wantedConnectorIndex)
647 continue;
648
649 drmModeConnectorPtr connector = drmModeGetConnector(fd: m_dri_fd, connectorId: resources->connectors[i]);
650 if (!connector)
651 continue;
652
653 ScreenInfo vinfo;
654 QPlatformScreen *screen = createScreenForConnector(resources, connector, vinfo: &vinfo);
655 if (screen)
656 screens.append(t: OrderedScreen(screen, vinfo));
657
658 drmModeFreeConnector(ptr: connector);
659 }
660
661 drmModeFreeResources(ptr: resources);
662
663 // Use stable sort to preserve the original (DRM connector) order
664 // for outputs with unspecified indices.
665 std::stable_sort(first: screens.begin(), last: screens.end(), comp: orderedScreenLessThan);
666 qCDebug(qLcKmsDebug) << "Sorted screen list:" << screens;
667
668 // The final list of screens is available, so do the second phase setup.
669 // Hook up clone sources and targets.
670 for (const OrderedScreen &orderedScreen : screens) {
671 QList<QPlatformScreen *> screensCloningThisScreen;
672 for (const OrderedScreen &s : screens) {
673 if (s.vinfo.output.clone_source == orderedScreen.vinfo.output.name)
674 screensCloningThisScreen.append(t: s.screen);
675 }
676 QPlatformScreen *screenThisScreenClones = nullptr;
677 if (!orderedScreen.vinfo.output.clone_source.isEmpty()) {
678 for (const OrderedScreen &s : screens) {
679 if (s.vinfo.output.name == orderedScreen.vinfo.output.clone_source) {
680 screenThisScreenClones = s.screen;
681 break;
682 }
683 }
684 }
685 if (screenThisScreenClones)
686 qCDebug(qLcKmsDebug) << orderedScreen.screen->name() << "clones" << screenThisScreenClones;
687 if (!screensCloningThisScreen.isEmpty())
688 qCDebug(qLcKmsDebug) << orderedScreen.screen->name() << "is cloned by" << screensCloningThisScreen;
689
690 registerScreenCloning(screen: orderedScreen.screen, screenThisScreenClones, screensCloningThisScreen);
691 }
692
693 // Figure out the virtual desktop and register the screens to QPA/QGuiApplication.
694 QPoint pos(0, 0);
695 QList<QPlatformScreen *> siblings;
696 QList<QPoint> virtualPositions;
697 int primarySiblingIdx = -1;
698
699 for (const OrderedScreen &orderedScreen : screens) {
700 QPlatformScreen *s = orderedScreen.screen;
701 QPoint virtualPos(0, 0);
702 // set up a horizontal or vertical virtual desktop
703 if (orderedScreen.vinfo.virtualPos.isNull()) {
704 virtualPos = pos;
705 if (m_screenConfig->virtualDesktopLayout() == QKmsScreenConfig::VirtualDesktopLayoutVertical)
706 pos.ry() += s->geometry().height();
707 else
708 pos.rx() += s->geometry().width();
709 } else {
710 virtualPos = orderedScreen.vinfo.virtualPos;
711 }
712 qCDebug(qLcKmsDebug) << "Adding QPlatformScreen" << s << "(" << s->name() << ")"
713 << "to QPA with geometry" << s->geometry()
714 << "and isPrimary=" << orderedScreen.vinfo.isPrimary;
715 // The order in qguiapp's screens list will match the order set by
716 // virtualIndex. This is not only handy but also required since for instance
717 // evdevtouch relies on it when performing touch device - screen mapping.
718 if (!m_screenConfig->separateScreens()) {
719 siblings.append(t: s);
720 virtualPositions.append(t: virtualPos);
721 if (orderedScreen.vinfo.isPrimary)
722 primarySiblingIdx = siblings.size() - 1;
723 } else {
724 registerScreen(screen: s, isPrimary: orderedScreen.vinfo.isPrimary, virtualPos, virtualSiblings: QList<QPlatformScreen *>() << s);
725 }
726 }
727
728 if (!m_screenConfig->separateScreens()) {
729 // enable the virtual desktop
730 for (int i = 0; i < siblings.size(); ++i)
731 registerScreen(screen: siblings[i], isPrimary: i == primarySiblingIdx, virtualPos: virtualPositions[i], virtualSiblings: siblings);
732 }
733}
734
735QPlatformScreen *QKmsDevice::createHeadlessScreen()
736{
737 // headless mode not supported by default
738 return nullptr;
739}
740
741// not all subclasses support screen cloning
742void QKmsDevice::registerScreenCloning(QPlatformScreen *screen,
743 QPlatformScreen *screenThisScreenClones,
744 const QList<QPlatformScreen *> &screensCloningThisScreen)
745{
746 Q_UNUSED(screen);
747 Q_UNUSED(screenThisScreenClones);
748 Q_UNUSED(screensCloningThisScreen);
749}
750
751// drm_property_type_is is not available in old headers
752static inline bool propTypeIs(drmModePropertyPtr prop, uint32_t type)
753{
754 if (prop->flags & DRM_MODE_PROP_EXTENDED_TYPE)
755 return (prop->flags & DRM_MODE_PROP_EXTENDED_TYPE) == type;
756 return prop->flags & type;
757}
758
759void QKmsDevice::enumerateProperties(drmModeObjectPropertiesPtr objProps, PropCallback callback)
760{
761 for (uint32_t propIdx = 0; propIdx < objProps->count_props; ++propIdx) {
762 drmModePropertyPtr prop = drmModeGetProperty(fd: m_dri_fd, propertyId: objProps->props[propIdx]);
763 if (!prop)
764 continue;
765
766 const quint64 value = objProps->prop_values[propIdx];
767 qCDebug(qLcKmsDebug, " property %d: id = %u name = '%s'", propIdx, prop->prop_id, prop->name);
768
769 if (propTypeIs(prop, DRM_MODE_PROP_SIGNED_RANGE)) {
770 qCDebug(qLcKmsDebug, " type is SIGNED_RANGE, value is %lld, possible values are:", qint64(value));
771 for (int i = 0; i < prop->count_values; ++i)
772 qCDebug(qLcKmsDebug, " %lld", qint64(prop->values[i]));
773 } else if (propTypeIs(prop, DRM_MODE_PROP_RANGE)) {
774 qCDebug(qLcKmsDebug, " type is RANGE, value is %llu, possible values are:", value);
775 for (int i = 0; i < prop->count_values; ++i)
776 qCDebug(qLcKmsDebug, " %llu", quint64(prop->values[i]));
777 } else if (propTypeIs(prop, DRM_MODE_PROP_ENUM)) {
778 qCDebug(qLcKmsDebug, " type is ENUM, value is %llu, possible values are:", value);
779 for (int i = 0; i < prop->count_enums; ++i)
780 qCDebug(qLcKmsDebug, " enum %d: %s - %llu", i, prop->enums[i].name, quint64(prop->enums[i].value));
781 } else if (propTypeIs(prop, DRM_MODE_PROP_BITMASK)) {
782 qCDebug(qLcKmsDebug, " type is BITMASK, value is %llu, possible bits are:", value);
783 for (int i = 0; i < prop->count_enums; ++i)
784 qCDebug(qLcKmsDebug, " bitmask %d: %s - %u", i, prop->enums[i].name, 1 << prop->enums[i].value);
785 } else if (propTypeIs(prop, DRM_MODE_PROP_BLOB)) {
786 qCDebug(qLcKmsDebug, " type is BLOB");
787 } else if (propTypeIs(prop, DRM_MODE_PROP_OBJECT)) {
788 qCDebug(qLcKmsDebug, " type is OBJECT");
789 }
790
791 callback(prop, value);
792
793 drmModeFreeProperty(ptr: prop);
794 }
795}
796
797void QKmsDevice::discoverPlanes()
798{
799 m_planes.clear();
800
801 drmModePlaneResPtr planeResources = drmModeGetPlaneResources(fd: m_dri_fd);
802 if (!planeResources)
803 return;
804
805 const int countPlanes = planeResources->count_planes;
806 qCDebug(qLcKmsDebug, "Found %d planes", countPlanes);
807 for (int planeIdx = 0; planeIdx < countPlanes; ++planeIdx) {
808 drmModePlanePtr drmplane = drmModeGetPlane(fd: m_dri_fd, plane_id: planeResources->planes[planeIdx]);
809 if (!drmplane) {
810 qCDebug(qLcKmsDebug, "Failed to query plane %d, ignoring", planeIdx);
811 continue;
812 }
813
814 QKmsPlane plane;
815 plane.id = drmplane->plane_id;
816 plane.possibleCrtcs = drmplane->possible_crtcs;
817
818 const int countFormats = drmplane->count_formats;
819 QString formatStr;
820 for (int i = 0; i < countFormats; ++i) {
821 uint32_t f = drmplane->formats[i];
822 plane.supportedFormats.append(t: f);
823 formatStr += QString::asprintf(format: "%c%c%c%c ", f, f >> 8, f >> 16, f >> 24);
824 }
825
826 qCDebug(qLcKmsDebug, "plane %d: id = %u countFormats = %d possibleCrtcs = 0x%x supported formats = %s",
827 planeIdx, plane.id, countFormats, plane.possibleCrtcs, qPrintable(formatStr));
828
829 drmModeFreePlane(ptr: drmplane);
830
831 drmModeObjectPropertiesPtr objProps = drmModeObjectGetProperties(fd: m_dri_fd, object_id: plane.id, DRM_MODE_OBJECT_PLANE);
832 if (!objProps) {
833 qCDebug(qLcKmsDebug, "Failed to query plane %d object properties, ignoring", planeIdx);
834 continue;
835 }
836
837 enumerateProperties(objProps, callback: [&plane](drmModePropertyPtr prop, quint64 value) {
838 if (!strcmp(s1: prop->name, s2: "type")) {
839 plane.type = QKmsPlane::Type(value);
840 } else if (!strcmp(s1: prop->name, s2: "rotation")) {
841 plane.initialRotation = QKmsPlane::Rotations(int(value));
842 plane.availableRotations = { };
843 if (propTypeIs(prop, DRM_MODE_PROP_BITMASK)) {
844 for (int i = 0; i < prop->count_enums; ++i)
845 plane.availableRotations |= QKmsPlane::Rotation(1 << prop->enums[i].value);
846 }
847 plane.rotationPropertyId = prop->prop_id;
848 } else if (!strcasecmp(s1: prop->name, s2: "crtc_id")) {
849 plane.crtcPropertyId = prop->prop_id;
850 } else if (!strcasecmp(s1: prop->name, s2: "fb_id")) {
851 plane.framebufferPropertyId = prop->prop_id;
852 } else if (!strcasecmp(s1: prop->name, s2: "src_w")) {
853 plane.srcwidthPropertyId = prop->prop_id;
854 } else if (!strcasecmp(s1: prop->name, s2: "src_h")) {
855 plane.srcheightPropertyId = prop->prop_id;
856 } else if (!strcasecmp(s1: prop->name, s2: "crtc_w")) {
857 plane.crtcwidthPropertyId = prop->prop_id;
858 } else if (!strcasecmp(s1: prop->name, s2: "crtc_h")) {
859 plane.crtcheightPropertyId = prop->prop_id;
860 } else if (!strcasecmp(s1: prop->name, s2: "src_x")) {
861 plane.srcXPropertyId = prop->prop_id;
862 } else if (!strcasecmp(s1: prop->name, s2: "src_y")) {
863 plane.srcYPropertyId = prop->prop_id;
864 } else if (!strcasecmp(s1: prop->name, s2: "crtc_x")) {
865 plane.crtcXPropertyId = prop->prop_id;
866 } else if (!strcasecmp(s1: prop->name, s2: "crtc_y")) {
867 plane.crtcYPropertyId = prop->prop_id;
868 } else if (!strcasecmp(s1: prop->name, s2: "zpos")) {
869 plane.zposPropertyId = prop->prop_id;
870 } else if (!strcasecmp(s1: prop->name, s2: "blend_op")) {
871 plane.blendOpPropertyId = prop->prop_id;
872 }
873 });
874
875 m_planes.append(t: plane);
876
877 drmModeFreeObjectProperties(ptr: objProps);
878 }
879
880 drmModeFreePlaneResources(ptr: planeResources);
881}
882
883int QKmsDevice::fd() const
884{
885 return m_dri_fd;
886}
887
888QString QKmsDevice::devicePath() const
889{
890 return m_path;
891}
892
893void QKmsDevice::setFd(int fd)
894{
895 m_dri_fd = fd;
896}
897
898
899bool QKmsDevice::hasAtomicSupport()
900{
901 return m_has_atomic_support;
902}
903
904#if QT_CONFIG(drm_atomic)
905drmModeAtomicReq *QKmsDevice::threadLocalAtomicRequest()
906{
907 if (!m_has_atomic_support)
908 return nullptr;
909
910 AtomicReqs &a(m_atomicReqs.localData());
911 if (!a.request)
912 a.request = drmModeAtomicAlloc();
913
914 return a.request;
915}
916
917bool QKmsDevice::threadLocalAtomicCommit(void *user_data)
918{
919 if (!m_has_atomic_support)
920 return false;
921
922 AtomicReqs &a(m_atomicReqs.localData());
923 if (!a.request)
924 return false;
925
926 int ret = drmModeAtomicCommit(fd: m_dri_fd, req: a.request,
927 DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_ALLOW_MODESET,
928 user_data);
929
930 if (ret) {
931 qWarning(msg: "Failed to commit atomic request (code=%d)", ret);
932 return false;
933 }
934
935 a.previous_request = a.request;
936 a.request = nullptr;
937
938 return true;
939}
940
941void QKmsDevice::threadLocalAtomicReset()
942{
943 if (!m_has_atomic_support)
944 return;
945
946 AtomicReqs &a(m_atomicReqs.localData());
947 if (a.previous_request) {
948 drmModeAtomicFree(req: a.previous_request);
949 a.previous_request = nullptr;
950 }
951}
952#endif
953
954void QKmsDevice::parseConnectorProperties(uint32_t connectorId, QKmsOutput *output)
955{
956 drmModeObjectPropertiesPtr objProps = drmModeObjectGetProperties(fd: m_dri_fd, object_id: connectorId, DRM_MODE_OBJECT_CONNECTOR);
957 if (!objProps) {
958 qCDebug(qLcKmsDebug, "Failed to query connector %d object properties", connectorId);
959 return;
960 }
961
962 enumerateProperties(objProps, callback: [output](drmModePropertyPtr prop, quint64 value) {
963 Q_UNUSED(value);
964 if (!strcasecmp(s1: prop->name, s2: "crtc_id"))
965 output->crtcIdPropertyId = prop->prop_id;
966 });
967
968 drmModeFreeObjectProperties(ptr: objProps);
969}
970
971void QKmsDevice::parseCrtcProperties(uint32_t crtcId, QKmsOutput *output)
972{
973 drmModeObjectPropertiesPtr objProps = drmModeObjectGetProperties(fd: m_dri_fd, object_id: crtcId, DRM_MODE_OBJECT_CRTC);
974 if (!objProps) {
975 qCDebug(qLcKmsDebug, "Failed to query crtc %d object properties", crtcId);
976 return;
977 }
978
979 enumerateProperties(objProps, callback: [output](drmModePropertyPtr prop, quint64 value) {
980 Q_UNUSED(value);
981 if (!strcasecmp(s1: prop->name, s2: "mode_id"))
982 output->modeIdPropertyId = prop->prop_id;
983 else if (!strcasecmp(s1: prop->name, s2: "active"))
984 output->activePropertyId = prop->prop_id;
985 });
986
987 drmModeFreeObjectProperties(ptr: objProps);
988}
989
990QKmsScreenConfig *QKmsDevice::screenConfig() const
991{
992 return m_screenConfig;
993}
994
995QKmsScreenConfig::QKmsScreenConfig()
996 : m_headless(false)
997 , m_hwCursor(true)
998 , m_separateScreens(false)
999 , m_pbuffers(false)
1000 , m_virtualDesktopLayout(VirtualDesktopLayoutHorizontal)
1001{
1002}
1003
1004void QKmsScreenConfig::loadConfig()
1005{
1006 QByteArray json = qgetenv(varName: "QT_QPA_EGLFS_KMS_CONFIG");
1007 if (json.isEmpty()) {
1008 json = qgetenv(varName: "QT_QPA_KMS_CONFIG");
1009 if (json.isEmpty())
1010 return;
1011 }
1012
1013 qCDebug(qLcKmsDebug) << "Loading KMS setup from" << json;
1014
1015 QFile file(QString::fromUtf8(ba: json));
1016 if (!file.open(flags: QFile::ReadOnly)) {
1017 qCWarning(qLcKmsDebug) << "Could not open config file"
1018 << json << "for reading";
1019 return;
1020 }
1021
1022 const QJsonDocument doc = QJsonDocument::fromJson(json: file.readAll());
1023 if (!doc.isObject()) {
1024 qCWarning(qLcKmsDebug) << "Invalid config file" << json
1025 << "- no top-level JSON object";
1026 return;
1027 }
1028
1029 const QJsonObject object = doc.object();
1030
1031 const QString headlessStr = object.value(key: "headless"_L1).toString();
1032 const QByteArray headless = headlessStr.toUtf8();
1033 QSize headlessSize;
1034 if (sscanf(s: headless.constData(), format: "%dx%d", &headlessSize.rwidth(), &headlessSize.rheight()) == 2) {
1035 m_headless = true;
1036 m_headlessSize = headlessSize;
1037 } else {
1038 m_headless = false;
1039 }
1040
1041 m_hwCursor = object.value(key: "hwcursor"_L1).toBool(defaultValue: m_hwCursor);
1042 m_pbuffers = object.value(key: "pbuffers"_L1).toBool(defaultValue: m_pbuffers);
1043 m_devicePath = object.value(key: "device"_L1).toString();
1044 m_separateScreens = object.value(key: "separateScreens"_L1).toBool(defaultValue: m_separateScreens);
1045
1046 const QString vdOriString = object.value(key: "virtualDesktopLayout"_L1).toString();
1047 if (!vdOriString.isEmpty()) {
1048 if (vdOriString == "horizontal"_L1)
1049 m_virtualDesktopLayout = VirtualDesktopLayoutHorizontal;
1050 else if (vdOriString == "vertical"_L1)
1051 m_virtualDesktopLayout = VirtualDesktopLayoutVertical;
1052 else
1053 qCWarning(qLcKmsDebug) << "Unknown virtualDesktopOrientation value" << vdOriString;
1054 }
1055
1056 const QJsonArray outputs = object.value(key: "outputs"_L1).toArray();
1057 for (int i = 0; i < outputs.size(); i++) {
1058 const QVariantMap outputSettings = outputs.at(i).toObject().toVariantMap();
1059
1060 if (outputSettings.contains(QStringLiteral("name"))) {
1061 const QString name = outputSettings.value(QStringLiteral("name")).toString();
1062
1063 if (m_outputSettings.contains(key: name)) {
1064 qCDebug(qLcKmsDebug) << "Output" << name << "configured multiple times!";
1065 }
1066
1067 m_outputSettings.insert(key: name, value: outputSettings);
1068 }
1069 }
1070
1071 qCDebug(qLcKmsDebug) << "Requested configuration (some settings may be ignored):\n"
1072 << "\theadless:" << m_headless << "\n"
1073 << "\thwcursor:" << m_hwCursor << "\n"
1074 << "\tpbuffers:" << m_pbuffers << "\n"
1075 << "\tseparateScreens:" << m_separateScreens << "\n"
1076 << "\tvirtualDesktopLayout:" << m_virtualDesktopLayout << "\n"
1077 << "\toutputs:" << m_outputSettings;
1078}
1079
1080void QKmsOutput::restoreMode(QKmsDevice *device)
1081{
1082 if (mode_set && saved_crtc) {
1083 drmModeSetCrtc(fd: device->fd(),
1084 crtcId: saved_crtc->crtc_id,
1085 bufferId: saved_crtc->buffer_id,
1086 x: 0, y: 0,
1087 connectors: &connector_id, count: 1,
1088 mode: &saved_crtc->mode);
1089 mode_set = false;
1090 }
1091}
1092
1093void QKmsOutput::cleanup(QKmsDevice *device)
1094{
1095 if (dpms_prop) {
1096 drmModeFreeProperty(ptr: dpms_prop);
1097 dpms_prop = nullptr;
1098 }
1099
1100 if (edid_blob) {
1101 drmModeFreePropertyBlob(ptr: edid_blob);
1102 edid_blob = nullptr;
1103 }
1104
1105 restoreMode(device);
1106
1107 if (saved_crtc) {
1108 drmModeFreeCrtc(ptr: saved_crtc);
1109 saved_crtc = nullptr;
1110 }
1111}
1112
1113QPlatformScreen::SubpixelAntialiasingType QKmsOutput::subpixelAntialiasingTypeHint() const
1114{
1115 switch (subpixel) {
1116 default:
1117 case DRM_MODE_SUBPIXEL_UNKNOWN:
1118 case DRM_MODE_SUBPIXEL_NONE:
1119 return QPlatformScreen::Subpixel_None;
1120 case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
1121 return QPlatformScreen::Subpixel_RGB;
1122 case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
1123 return QPlatformScreen::Subpixel_BGR;
1124 case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
1125 return QPlatformScreen::Subpixel_VRGB;
1126 case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
1127 return QPlatformScreen::Subpixel_VBGR;
1128 }
1129}
1130
1131void QKmsOutput::setPowerState(QKmsDevice *device, QPlatformScreen::PowerState state)
1132{
1133 if (dpms_prop)
1134 drmModeConnectorSetProperty(fd: device->fd(), connector_id,
1135 property_id: dpms_prop->prop_id, value: (int) state);
1136}
1137
1138QT_END_NAMESPACE
1139

source code of qtbase/src/platformsupport/kmsconvenience/qkmsdevice.cpp