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 
58 QT_BEGIN_NAMESPACE
59 
60 Q_LOGGING_CATEGORY(qLcFbDrm, "qt.qpa.fb")
61 
62 static const int BUFFER_COUNT = 2;
63 
64 class QLinuxFbDevice : public QKmsDevice
65 {
66 public:
67     struct Framebuffer {
FramebufferQLinuxFbDevice::Framebuffer68         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 {
OutputQLinuxFbDevice::Output78         Output() : backFb(0), flipped(false) { }
79         QKmsOutput kmsOutput;
80         Framebuffer fb[BUFFER_COUNT];
81         QRegion dirty[BUFFER_COUNT];
82         int backFb;
83         bool flipped;
currentResQLinuxFbDevice::Output84         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 
outputCount() const101     int outputCount() const { return m_outputs.count(); }
output(int idx)102     Output *output(int idx) { return &m_outputs[idx]; }
103 
104 private:
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     QVector<Output> m_outputs;
119 };
120 
QLinuxFbDevice(QKmsScreenConfig * screenConfig)121 QLinuxFbDevice::QLinuxFbDevice(QKmsScreenConfig *screenConfig)
122     : QKmsDevice(screenConfig, QStringLiteral("/dev/dri/card0"))
123 {
124 }
125 
open()126 bool 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 
close()148 void 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 
nativeDisplay() const162 void *QLinuxFbDevice::nativeDisplay() const
163 {
164     Q_UNREACHABLE();
165     return nullptr;
166 }
167 
createScreen(const QKmsOutput & output)168 QPlatformScreen *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 
registerScreen(QPlatformScreen * screen,bool isPrimary,const QPoint & virtualPos,const QList<QPlatformScreen * > & virtualSiblings)177 void 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 
bppForDrmFormat(uint32_t drmFormat)189 static 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 
depthForDrmFormat(uint32_t drmFormat)200 static 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 
formatForDrmFormat(uint32_t drmFormat)217 static 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 
createFramebuffer(QLinuxFbDevice::Output * output,int bufferIdx)240 bool 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 
createFramebuffers()296 void 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 
destroyFramebuffer(QLinuxFbDevice::Output * output,int bufferIdx)308 void 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 
destroyFramebuffers()325 void 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 
setMode()333 void 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 
pageFlipHandler(int fd,unsigned int sequence,unsigned int tv_sec,unsigned int tv_usec,void * user_data)348 void 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 
swapBuffers(Output * output)361 void 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 
QLinuxFbDrmScreen(const QStringList & args)382 QLinuxFbDrmScreen::QLinuxFbDrmScreen(const QStringList &args)
383     : m_screenConfig(nullptr),
384       m_device(nullptr)
385 {
386     Q_UNUSED(args);
387 }
388 
~QLinuxFbDrmScreen()389 QLinuxFbDrmScreen::~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 
initialize()399 bool QLinuxFbDrmScreen::initialize()
400 {
401     m_screenConfig = new QKmsScreenConfig;
402     m_device = new QLinuxFbDevice(m_screenConfig);
403     if (!m_device->open())
404         return false;
405 
406     // Discover outputs. Calls back Device::createScreen().
407     m_device->createScreens();
408     // Now off to dumb buffer specifics.
409     m_device->createFramebuffers();
410     // Do the modesetting.
411     m_device->setMode();
412 
413     QLinuxFbDevice::Output *output(m_device->output(0));
414 
415     mGeometry = QRect(QPoint(0, 0), output->currentRes());
416     mDepth = depthForDrmFormat(output->kmsOutput.drm_format);
417     mFormat = formatForDrmFormat(output->kmsOutput.drm_format);
418     mPhysicalSize = output->kmsOutput.physical_size;
419     qCDebug(qLcFbDrm) << mGeometry << mPhysicalSize << mDepth << mFormat;
420 
421     QFbScreen::initializeCompositor();
422 
423     mCursor = new QFbCursor(this);
424 
425     return true;
426 }
427 
doRedraw()428 QRegion QLinuxFbDrmScreen::doRedraw()
429 {
430     const QRegion dirty = QFbScreen::doRedraw();
431     if (dirty.isEmpty())
432         return dirty;
433 
434     QLinuxFbDevice::Output *output(m_device->output(0));
435 
436     for (int i = 0; i < BUFFER_COUNT; ++i)
437         output->dirty[i] += dirty;
438 
439     if (output->fb[output->backFb].wrapper.isNull())
440         return dirty;
441 
442     QPainter pntr(&output->fb[output->backFb].wrapper);
443     // Image has alpha but no need for blending at this stage.
444     // Do not waste time with the default SourceOver.
445     pntr.setCompositionMode(QPainter::CompositionMode_Source);
446     for (const QRect &rect : qAsConst(output->dirty[output->backFb]))
447         pntr.drawImage(rect, mScreenImage, rect);
448     pntr.end();
449 
450     output->dirty[output->backFb] = QRegion();
451 
452     m_device->swapBuffers(output);
453 
454     return dirty;
455 }
456 
grabWindow(WId wid,int x,int y,int width,int height) const457 QPixmap QLinuxFbDrmScreen::grabWindow(WId wid, int x, int y, int width, int height) const
458 {
459     Q_UNUSED(wid);
460     Q_UNUSED(x);
461     Q_UNUSED(y);
462     Q_UNUSED(width);
463     Q_UNUSED(height);
464 
465     return QPixmap();
466 }
467 
468 QT_END_NAMESPACE
469