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