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

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