1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40// Experimental DRM dumb buffer backend.
41//
42// TODO:
43// Multiscreen: QWindow-QScreen(-output) association. Needs some reorg (device cannot be owned by screen)
44// Find card via devicediscovery like in eglfs_kms.
45// Mode restore like QEglFSKmsInterruptHandler.
46// grabWindow
47
48#include "qlinuxfbdrmscreen.h"
49#include <QLoggingCategory>
50#include <QGuiApplication>
51#include <QPainter>
52#include <QtFbSupport/private/qfbcursor_p.h>
53#include <QtFbSupport/private/qfbwindow_p.h>
54#include <QtKmsSupport/private/qkmsdevice_p.h>
55#include <QtCore/private/qcore_unix_p.h>
56#include <sys/mman.h>
57
58QT_BEGIN_NAMESPACE
59
60Q_LOGGING_CATEGORY(qLcFbDrm, "qt.qpa.fb")
61
62static const int BUFFER_COUNT = 2;
63
64class QLinuxFbDevice : public QKmsDevice
65{
66public:
67 struct Framebuffer {
68 Framebuffer() : handle(0), pitch(0), size(0), fb(0), p(MAP_FAILED) { }
69 uint32_t handle;
70 uint32_t pitch;
71 uint64_t size;
72 uint32_t fb;
73 void *p;
74 QImage wrapper;
75 };
76
77 struct Output {
78 Output() : backFb(0), flipped(false) { }
79 QKmsOutput kmsOutput;
80 Framebuffer fb[BUFFER_COUNT];
81 QRegion dirty[BUFFER_COUNT];
82 int backFb;
83 bool flipped;
84 QSize currentRes() const {
85 const drmModeModeInfo &modeInfo(kmsOutput.modes[kmsOutput.mode]);
86 return QSize(modeInfo.hdisplay, modeInfo.vdisplay);
87 }
88 };
89
90 QLinuxFbDevice(QKmsScreenConfig *screenConfig);
91
92 bool open() override;
93 void close() override;
94
95 void createFramebuffers();
96 void destroyFramebuffers();
97 void setMode();
98
99 void swapBuffers(Output *output);
100
101 int outputCount() const { return m_outputs.count(); }
102 Output *output(int idx) { return &m_outputs[idx]; }
103
104private:
105 void *nativeDisplay() const override;
106 QPlatformScreen *createScreen(const QKmsOutput &output) override;
107 void registerScreen(QPlatformScreen *screen,
108 bool isPrimary,
109 const QPoint &virtualPos,
110 const QList<QPlatformScreen *> &virtualSiblings) override;
111
112 bool createFramebuffer(Output *output, int bufferIdx);
113 void destroyFramebuffer(Output *output, int bufferIdx);
114
115 static void pageFlipHandler(int fd, unsigned int sequence,
116 unsigned int tv_sec, unsigned int tv_usec, void *user_data);
117
118 QList<Output> m_outputs;
119};
120
121QLinuxFbDevice::QLinuxFbDevice(QKmsScreenConfig *screenConfig)
122 : QKmsDevice(screenConfig, QStringLiteral("/dev/dri/card0"))
123{
124}
125
126bool QLinuxFbDevice::open()
127{
128 int fd = qt_safe_open(devicePath().toLocal8Bit().constData(), O_RDWR | O_CLOEXEC);
129 if (fd == -1) {
130 qErrnoWarning("Could not open DRM device %s", qPrintable(devicePath()));
131 return false;
132 }
133
134 uint64_t hasDumbBuf = 0;
135 if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &hasDumbBuf) == -1 || !hasDumbBuf) {
136 qWarning("Dumb buffers not supported");
137 qt_safe_close(fd);
138 return false;
139 }
140
141 setFd(fd);
142
143 qCDebug(qLcFbDrm, "DRM device %s opened", qPrintable(devicePath()));
144
145 return true;
146}
147
148void QLinuxFbDevice::close()
149{
150 for (Output &output : m_outputs)
151 output.kmsOutput.cleanup(this); // restore mode
152
153 m_outputs.clear();
154
155 if (fd() != -1) {
156 qCDebug(qLcFbDrm, "Closing DRM device");
157 qt_safe_close(fd());
158 setFd(-1);
159 }
160}
161
162void *QLinuxFbDevice::nativeDisplay() const
163{
164 Q_UNREACHABLE();
165 return nullptr;
166}
167
168QPlatformScreen *QLinuxFbDevice::createScreen(const QKmsOutput &output)
169{
170 qCDebug(qLcFbDrm, "Got a new output: %s", qPrintable(output.name));
171 Output o;
172 o.kmsOutput = output;
173 m_outputs.append(o);
174 return nullptr; // no platformscreen, we are not a platform plugin
175}
176
177void QLinuxFbDevice::registerScreen(QPlatformScreen *screen,
178 bool isPrimary,
179 const QPoint &virtualPos,
180 const QList<QPlatformScreen *> &virtualSiblings)
181{
182 Q_UNUSED(screen);
183 Q_UNUSED(isPrimary);
184 Q_UNUSED(virtualPos);
185 Q_UNUSED(virtualSiblings);
186 Q_UNREACHABLE();
187}
188
189static uint32_t bppForDrmFormat(uint32_t drmFormat)
190{
191 switch (drmFormat) {
192 case DRM_FORMAT_RGB565:
193 case DRM_FORMAT_BGR565:
194 return 16;
195 default:
196 return 32;
197 }
198}
199
200static int depthForDrmFormat(uint32_t drmFormat)
201{
202 switch (drmFormat) {
203 case DRM_FORMAT_RGB565:
204 case DRM_FORMAT_BGR565:
205 return 16;
206 case DRM_FORMAT_XRGB8888:
207 case DRM_FORMAT_XBGR8888:
208 return 24;
209 case DRM_FORMAT_XRGB2101010:
210 case DRM_FORMAT_XBGR2101010:
211 return 30;
212 default:
213 return 32;
214 }
215}
216
217static QImage::Format formatForDrmFormat(uint32_t drmFormat)
218{
219 switch (drmFormat) {
220 case DRM_FORMAT_XRGB8888:
221 case DRM_FORMAT_XBGR8888:
222 return QImage::Format_RGB32;
223 case DRM_FORMAT_ARGB8888:
224 case DRM_FORMAT_ABGR8888:
225 return QImage::Format_ARGB32;
226 case DRM_FORMAT_RGB565:
227 case DRM_FORMAT_BGR565:
228 return QImage::Format_RGB16;
229 case DRM_FORMAT_XRGB2101010:
230 case DRM_FORMAT_XBGR2101010:
231 return QImage::Format_RGB30;
232 case DRM_FORMAT_ARGB2101010:
233 case DRM_FORMAT_ABGR2101010:
234 return QImage::Format_A2RGB30_Premultiplied;
235 default:
236 return QImage::Format_ARGB32;
237 }
238}
239
240bool QLinuxFbDevice::createFramebuffer(QLinuxFbDevice::Output *output, int bufferIdx)
241{
242 const QSize size = output->currentRes();
243 const uint32_t w = size.width();
244 const uint32_t h = size.height();
245 const uint32_t bpp = bppForDrmFormat(output->kmsOutput.drm_format);
246 drm_mode_create_dumb creq = {
247 h,
248 w,
249 bpp,
250 0, 0, 0, 0
251 };
252 if (drmIoctl(fd(), DRM_IOCTL_MODE_CREATE_DUMB, &creq) == -1) {
253 qErrnoWarning(errno, "Failed to create dumb buffer");
254 return false;
255 }
256
257 Framebuffer &fb(output->fb[bufferIdx]);
258 fb.handle = creq.handle;
259 fb.pitch = creq.pitch;
260 fb.size = creq.size;
261 qCDebug(qLcFbDrm, "Got a dumb buffer for size %dx%d and bpp %u: handle %u, pitch %u, size %u",
262 w, h, bpp, fb.handle, fb.pitch, (uint) fb.size);
263
264 uint32_t handles[4] = { fb.handle };
265 uint32_t strides[4] = { fb.pitch };
266 uint32_t offsets[4] = { 0 };
267
268 if (drmModeAddFB2(fd(), w, h, output->kmsOutput.drm_format,
269 handles, strides, offsets, &fb.fb, 0) == -1) {
270 qErrnoWarning(errno, "Failed to add FB");
271 return false;
272 }
273
274 drm_mode_map_dumb mreq = {
275 fb.handle,
276 0, 0
277 };
278 if (drmIoctl(fd(), DRM_IOCTL_MODE_MAP_DUMB, &mreq) == -1) {
279 qErrnoWarning(errno, "Failed to map dumb buffer");
280 return false;
281 }
282 fb.p = mmap(0, fb.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd(), mreq.offset);
283 if (fb.p == MAP_FAILED) {
284 qErrnoWarning(errno, "Failed to mmap dumb buffer");
285 return false;
286 }
287
288 qCDebug(qLcFbDrm, "FB is %u (DRM format 0x%x), mapped at %p", fb.fb, output->kmsOutput.drm_format, fb.p);
289 memset(fb.p, 0, fb.size);
290
291 fb.wrapper = QImage(static_cast<uchar *>(fb.p), w, h, fb.pitch, formatForDrmFormat(output->kmsOutput.drm_format));
292
293 return true;
294}
295
296void QLinuxFbDevice::createFramebuffers()
297{
298 for (Output &output : m_outputs) {
299 for (int i = 0; i < BUFFER_COUNT; ++i) {
300 if (!createFramebuffer(&output, i))
301 return;
302 }
303 output.backFb = 0;
304 output.flipped = false;
305 }
306}
307
308void QLinuxFbDevice::destroyFramebuffer(QLinuxFbDevice::Output *output, int bufferIdx)
309{
310 Framebuffer &fb(output->fb[bufferIdx]);
311 if (fb.p != MAP_FAILED)
312 munmap(fb.p, fb.size);
313 if (fb.fb) {
314 if (drmModeRmFB(fd(), fb.fb) == -1)
315 qErrnoWarning("Failed to remove fb");
316 }
317 if (fb.handle) {
318 drm_mode_destroy_dumb dreq = { fb.handle };
319 if (drmIoctl(fd(), DRM_IOCTL_MODE_DESTROY_DUMB, &dreq) == -1)
320 qErrnoWarning(errno, "Failed to destroy dumb buffer %u", fb.handle);
321 }
322 fb = Framebuffer();
323}
324
325void QLinuxFbDevice::destroyFramebuffers()
326{
327 for (Output &output : m_outputs) {
328 for (int i = 0; i < BUFFER_COUNT; ++i)
329 destroyFramebuffer(&output, i);
330 }
331}
332
333void QLinuxFbDevice::setMode()
334{
335 for (Output &output : m_outputs) {
336 drmModeModeInfo &modeInfo(output.kmsOutput.modes[output.kmsOutput.mode]);
337 if (drmModeSetCrtc(fd(), output.kmsOutput.crtc_id, output.fb[0].fb, 0, 0,
338 &output.kmsOutput.connector_id, 1, &modeInfo) == -1) {
339 qErrnoWarning(errno, "Failed to set mode");
340 return;
341 }
342
343 output.kmsOutput.mode_set = true; // have cleanup() to restore the mode
344 output.kmsOutput.setPowerState(this, QPlatformScreen::PowerStateOn);
345 }
346}
347
348void QLinuxFbDevice::pageFlipHandler(int fd, unsigned int sequence,
349 unsigned int tv_sec, unsigned int tv_usec,
350 void *user_data)
351{
352 Q_UNUSED(fd);
353 Q_UNUSED(sequence);
354 Q_UNUSED(tv_sec);
355 Q_UNUSED(tv_usec);
356
357 Output *output = static_cast<Output *>(user_data);
358 output->backFb = (output->backFb + 1) % BUFFER_COUNT;
359}
360
361void QLinuxFbDevice::swapBuffers(Output *output)
362{
363 Framebuffer &fb(output->fb[output->backFb]);
364 if (drmModePageFlip(fd(), output->kmsOutput.crtc_id, fb.fb, DRM_MODE_PAGE_FLIP_EVENT, output) == -1) {
365 qErrnoWarning(errno, "Page flip failed");
366 return;
367 }
368
369 const int fbIdx = output->backFb;
370 while (output->backFb == fbIdx) {
371 drmEventContext drmEvent;
372 memset(&drmEvent, 0, sizeof(drmEvent));
373 drmEvent.version = 2;
374 drmEvent.vblank_handler = nullptr;
375 drmEvent.page_flip_handler = pageFlipHandler;
376 // Blocks until there is something to read on the drm fd
377 // and calls back pageFlipHandler once the flip completes.
378 drmHandleEvent(fd(), &drmEvent);
379 }
380}
381
382QLinuxFbDrmScreen::QLinuxFbDrmScreen(const QStringList &args)
383 : m_screenConfig(nullptr),
384 m_device(nullptr)
385{
386 Q_UNUSED(args);
387}
388
389QLinuxFbDrmScreen::~QLinuxFbDrmScreen()
390{
391 if (m_device) {
392 m_device->destroyFramebuffers();
393 m_device->close();
394 delete m_device;
395 }
396 delete m_screenConfig;
397}
398
399bool QLinuxFbDrmScreen::initialize()
400{
401 m_screenConfig = new QKmsScreenConfig;
402 m_screenConfig->loadConfig();
403 m_device = new QLinuxFbDevice(m_screenConfig);
404 if (!m_device->open())
405 return false;
406
407 // Discover outputs. Calls back Device::createScreen().
408 m_device->createScreens();
409 // Now off to dumb buffer specifics.
410 m_device->createFramebuffers();
411 // Do the modesetting.
412 m_device->setMode();
413
414 QLinuxFbDevice::Output *output(m_device->output(0));
415
416 mGeometry = QRect(QPoint(0, 0), output->currentRes());
417 mDepth = depthForDrmFormat(output->kmsOutput.drm_format);
418 mFormat = formatForDrmFormat(output->kmsOutput.drm_format);
419 mPhysicalSize = output->kmsOutput.physical_size;
420 qCDebug(qLcFbDrm) << mGeometry << mPhysicalSize << mDepth << mFormat;
421
422 QFbScreen::initializeCompositor();
423
424 mCursor = new QFbCursor(this);
425
426 return true;
427}
428
429QRegion QLinuxFbDrmScreen::doRedraw()
430{
431 const QRegion dirty = QFbScreen::doRedraw();
432 if (dirty.isEmpty())
433 return dirty;
434
435 QLinuxFbDevice::Output *output(m_device->output(0));
436
437 for (int i = 0; i < BUFFER_COUNT; ++i)
438 output->dirty[i] += dirty;
439
440 if (output->fb[output->backFb].wrapper.isNull())
441 return dirty;
442
443 QPainter pntr(&output->fb[output->backFb].wrapper);
444 // Image has alpha but no need for blending at this stage.
445 // Do not waste time with the default SourceOver.
446 pntr.setCompositionMode(QPainter::CompositionMode_Source);
447 for (const QRect &rect : qAsConst(output->dirty[output->backFb]))
448 pntr.drawImage(rect, mScreenImage, rect);
449 pntr.end();
450
451 output->dirty[output->backFb] = QRegion();
452
453 m_device->swapBuffers(output);
454
455 return dirty;
456}
457
458QPixmap QLinuxFbDrmScreen::grabWindow(WId wid, int x, int y, int width, int height) const
459{
460 Q_UNUSED(wid);
461 Q_UNUSED(x);
462 Q_UNUSED(y);
463 Q_UNUSED(width);
464 Q_UNUSED(height);
465
466 return QPixmap();
467}
468
469QT_END_NAMESPACE
470