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 QVector<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(pathname: devicePath().toLocal8Bit().constData(), O_RDWR | O_CLOEXEC);
129 if (fd == -1) {
130 qErrnoWarning(msg: "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, value: &hasDumbBuf) == -1 || !hasDumbBuf) {
136 qWarning(msg: "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(device: this); // restore mode
152
153 m_outputs.clear();
154
155 if (fd() != -1) {
156 qCDebug(qLcFbDrm, "Closing DRM device");
157 qt_safe_close(fd: 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(t: 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(drmFormat: output->kmsOutput.drm_format);
246 drm_mode_create_dumb creq = {
247 .height: h,
248 .width: w,
249 .bpp: bpp,
250 .flags: 0, .handle: 0, .pitch: 0, .size: 0
251 };
252 if (drmIoctl(fd: fd(), DRM_IOCTL_MODE_CREATE_DUMB, arg: &creq) == -1) {
253 qErrnoWarning(errno, msg: "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: fd(), width: w, height: h, pixel_format: output->kmsOutput.drm_format,
269 bo_handles: handles, pitches: strides, offsets, buf_id: &fb.fb, flags: 0) == -1) {
270 qErrnoWarning(errno, msg: "Failed to add FB");
271 return false;
272 }
273
274 drm_mode_map_dumb mreq = {
275 .handle: fb.handle,
276 .pad: 0, .offset: 0
277 };
278 if (drmIoctl(fd: fd(), DRM_IOCTL_MODE_MAP_DUMB, arg: &mreq) == -1) {
279 qErrnoWarning(errno, msg: "Failed to map dumb buffer");
280 return false;
281 }
282 fb.p = mmap(addr: 0, len: fb.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd: fd(), offset: mreq.offset);
283 if (fb.p == MAP_FAILED) {
284 qErrnoWarning(errno, msg: "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(s: fb.p, c: 0, n: fb.size);
290
291 fb.wrapper = QImage(static_cast<uchar *>(fb.p), w, h, fb.pitch, formatForDrmFormat(drmFormat: 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: &output, bufferIdx: 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(addr: fb.p, len: fb.size);
313 if (fb.fb) {
314 if (drmModeRmFB(fd: fd(), bufferId: fb.fb) == -1)
315 qErrnoWarning(msg: "Failed to remove fb");
316 }
317 if (fb.handle) {
318 drm_mode_destroy_dumb dreq = { .handle: fb.handle };
319 if (drmIoctl(fd: fd(), DRM_IOCTL_MODE_DESTROY_DUMB, arg: &dreq) == -1)
320 qErrnoWarning(errno, msg: "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: &output, bufferIdx: 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: fd(), crtcId: output.kmsOutput.crtc_id, bufferId: output.fb[0].fb, x: 0, y: 0,
338 connectors: &output.kmsOutput.connector_id, count: 1, mode: &modeInfo) == -1) {
339 qErrnoWarning(errno, msg: "Failed to set mode");
340 return;
341 }
342
343 output.kmsOutput.mode_set = true; // have cleanup() to restore the mode
344 output.kmsOutput.setPowerState(device: this, state: 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: fd(), crtc_id: output->kmsOutput.crtc_id, fb_id: fb.fb, DRM_MODE_PAGE_FLIP_EVENT, user_data: output) == -1) {
365 qErrnoWarning(errno, msg: "Page flip failed");
366 return;
367 }
368
369 const int fbIdx = output->backFb;
370 while (output->backFb == fbIdx) {
371 drmEventContext drmEvent;
372 memset(s: &drmEvent, c: 0, n: 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: fd(), evctx: &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_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(idx: 0));
414
415 mGeometry = QRect(QPoint(0, 0), output->currentRes());
416 mDepth = depthForDrmFormat(drmFormat: output->kmsOutput.drm_format);
417 mFormat = formatForDrmFormat(drmFormat: 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
428QRegion QLinuxFbDrmScreen::doRedraw()
429{
430 const QRegion dirty = QFbScreen::doRedraw();
431 if (dirty.isEmpty())
432 return dirty;
433
434 QLinuxFbDevice::Output *output(m_device->output(idx: 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(t&: output->dirty[output->backFb]))
447 pntr.drawImage(targetRect: rect, image: mScreenImage, sourceRect: rect);
448 pntr.end();
449
450 output->dirty[output->backFb] = QRegion();
451
452 m_device->swapBuffers(output);
453
454 return dirty;
455}
456
457QPixmap 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
468QT_END_NAMESPACE
469

source code of qtbase/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp