Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
linuxdmabuf.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:critical reason:network-protocol
4
5#include "linuxdmabuf.h"
7
8#include <QtCore/QStandardPaths>
9
10#include <QtWaylandCompositor/QWaylandCompositor>
11#include <QtWaylandCompositor/private/qwltextureorphanage_p.h>
12
13#include <drm_fourcc.h>
14#include <drm_mode.h>
15#include <unistd.h>
16#include <sys/stat.h>
17
18#include <fcntl.h>
19#include <sys/mman.h>
20
21#ifdef Q_OS_LINUX
22# include <sys/syscall.h>
23// from linux/memfd.h:
24# ifndef MFD_CLOEXEC
25# define MFD_CLOEXEC 0x0001U
26# endif
27# ifndef MFD_ALLOW_SEALING
28# define MFD_ALLOW_SEALING 0x0002U
29# endif
30// from bits/fcntl-linux.h
31# ifndef F_ADD_SEALS
32# define F_ADD_SEALS 1033
33# endif
34# ifndef F_SEAL_SEAL
35# define F_SEAL_SEAL 0x0001
36# endif
37# ifndef F_SEAL_SHRINK
38# define F_SEAL_SHRINK 0x0002
39# endif
40#endif
41
42QT_BEGIN_NAMESPACE
43
44LinuxDmabuf::LinuxDmabuf(int version,
45 wl_display *display,
46 LinuxDmabufClientBufferIntegration *clientBufferIntegration)
47 : zwp_linux_dmabuf_v1(display, version)
48 , m_clientBufferIntegration(clientBufferIntegration)
49{
50}
51
52void LinuxDmabuf::setDrmDevice(const char *drmDevice)
53{
54 m_drmDevice = drmDevice;
55}
56
57void LinuxDmabuf::setSupportedModifiers(const QHash<uint32_t, QList<uint64_t>> &modifiers)
58{
59 Q_ASSERT(resourceMap().isEmpty());
60 m_modifiers = modifiers;
61}
62
64{
65 if (resource->version() >= ZWP_LINUX_DMABUF_V1_GET_SURFACE_FEEDBACK_SINCE_VERSION)
66 return;
67
68 for (auto it = m_modifiers.constBegin(); it != m_modifiers.constEnd(); ++it) {
69 auto format = it.key();
70 auto modifiers = it.value();
71 // send DRM_FORMAT_MOD_INVALID when no modifiers are supported for a format
72 if (modifiers.isEmpty())
73 modifiers << DRM_FORMAT_MOD_INVALID;
74 for (const auto &modifier : std::as_const(modifiers)) {
75 if (resource->version() >= ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) {
76 const uint32_t modifier_lo = modifier & 0xFFFFFFFF;
77 const uint32_t modifier_hi = modifier >> 32;
78 send_modifier(resource->handle, format, modifier_hi, modifier_lo);
79 } else if (modifier == DRM_FORMAT_MOD_LINEAR || modifier == DRM_FORMAT_MOD_INVALID) {
80 send_format(resource->handle, format);
81 }
82 }
83 }
84}
85
86void LinuxDmabuf::zwp_linux_dmabuf_v1_create_params(Resource *resource, uint32_t params_id)
87{
88 wl_resource *r = wl_resource_create(resource->client(), &zwp_linux_buffer_params_v1_interface,
89 wl_resource_get_version(resource->handle), params_id);
90 new LinuxDmabufParams(m_clientBufferIntegration, r); // deleted by the client, or when it disconnects
91}
92
93void LinuxDmabuf::zwp_linux_dmabuf_v1_get_default_feedback(Resource *resource, uint32_t id)
94{
95 if (resource->version() < ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION)
96 return;
97
98 wl_resource *r = wl_resource_create(resource->client(),
99 &zwp_linux_dmabuf_feedback_v1_interface,
100 wl_resource_get_version(resource->handle),
101 id);
102 // Deleted by client
103 new LinuxDmabufFeedback(m_modifiers, m_drmDevice, r);
104}
105
106void LinuxDmabuf::zwp_linux_dmabuf_v1_get_surface_feedback(Resource *resource, uint32_t id, struct ::wl_resource *surface)
107{
108 if (resource->version() < ZWP_LINUX_DMABUF_V1_GET_SURFACE_FEEDBACK_SINCE_VERSION)
109 return;
110
111 Q_UNUSED(surface);
112 wl_resource *r = wl_resource_create(resource->client(),
113 &zwp_linux_dmabuf_feedback_v1_interface,
114 wl_resource_get_version(resource->handle),
115 id);
116 // Deleted by client
117 new LinuxDmabufFeedback(m_modifiers, m_drmDevice, r);
118}
119
120LinuxDmabufFeedback::LinuxDmabufFeedback(QHash<uint32_t, QList<uint64_t>> modifiers,
121 const char *drmDevice, wl_resource *res)
122 : zwp_linux_dmabuf_feedback_v1(res)
123 , m_modifiers(modifiers)
124 , m_drmDevice(drmDevice)
125{
126 sendFeedback(resource());
127}
128
130{
131 if (m_data)
132 munmap(m_data, m_size);
133}
134
136{
137 wl_resource_destroy(resource->handle);
138}
139
141{
142 Q_UNUSED(resource);
143 delete this;
144}
145
147{
148 sendFeedback(resource);
149}
150
151QByteArray LinuxDmabufFeedback::sendFormatTable(Resource *)
152{
153 QList<std::pair<uint32_t, uint64_t> > formatModifierPairs;
154 for (auto it = m_modifiers.constBegin(); it != m_modifiers.constEnd(); ++it) {
155 uint32_t format = it.key();
156 QList<uint64_t> modifiers = it.value();
157
158 if (!modifiers.isEmpty()) {
159 for (uint64_t modifier : modifiers)
160 formatModifierPairs.append(std::make_pair(format, modifier));
161 } else {
162 formatModifierPairs.append(std::make_pair(format, DRM_FORMAT_MOD_INVALID));
163 }
164 }
165
166 if (formatModifierPairs.isEmpty()) {
167 qCWarning(qLcWaylandCompositorHardwareIntegration) << "LinuxDmabufFeedback: No formats";
168 return QByteArray{};
169 }
170
171 int fd = -1;
172#ifdef SYS_memfd_create
173 fd = syscall(SYS_memfd_create, "wayland-dmabuf", MFD_CLOEXEC | MFD_ALLOW_SEALING);
174 if (fd >= 0)
175 fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
176#endif
177
178 std::unique_ptr<QFile> filePointer;
179 if (fd == -1) {
180 auto tmpFile =
181 std::make_unique<QTemporaryFile>(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) +
182 QLatin1String("/wayland-dmabuf-XXXXXX"));
183 if (tmpFile->open())
184 filePointer = std::move(tmpFile);
185 } else {
186 auto file = std::make_unique<QFile>();
187 if (file->open(fd, QIODevice::ReadWrite | QIODevice::Unbuffered, QFile::AutoCloseHandle))
188 filePointer = std::move(file);
189 }
190
191 m_size = formatModifierPairs.size() * 16;
192 if (!filePointer || !filePointer->resize(m_size)) {
193 qCWarning(qLcWaylandCompositorHardwareIntegration)
194 << "LinuxDmabufFeedback: failed: " << filePointer->errorString();
195 return QByteArray{};
196 }
197
198 fd = filePointer->handle();
199
200 if (m_data)
201 munmap(m_data, m_size);
202 m_data = (uchar *) mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
203
204 QByteArray indicesArray;
205 indicesArray.resize(formatModifierPairs.size() * 2); // Each index is 2 bytes
206 char *iptr = indicesArray.data();
207
208 // Copy data into array with native endianness
209 for (quint16 i = 0; i < quint16(formatModifierPairs.size()); ++i) {
210 memcpy(iptr, &i, 2);
211 iptr += 2;
212
213 const auto &formatModifierPair = formatModifierPairs.at(i);
214 uint32_t format = formatModifierPair.first;
215 uint64_t modifiers = formatModifierPair.second;
216 memcpy(m_data, &format, 4);
217 m_data += 4;
218
219 m_data += 4; // Padding
220
221 memcpy(m_data, &modifiers, 8);
222 m_data += 8;
223 }
224
225 send_format_table(fd, m_size);
226
227 // Returns indices for convenience
228 return indicesArray;
229}
230
231void LinuxDmabufFeedback::sendFeedback(Resource *resource)
232{
233 if (!m_drmDevice)
234 return;
235
236 QByteArray indices = sendFormatTable(resource);
237
238 // Query main device by getting the device number of the drm device,
239 // granted its still there.
240 struct stat drmDeviceStat;
241 if (stat(m_drmDevice, &drmDeviceStat)) {
242 qCWarning(qLcWaylandCompositorHardwareIntegration)
243 << "Failed to access DRM device in linux-dmabuf-feedback";
244 return;
245 }
246
247 dev_t mainDevice = drmDeviceStat.st_rdev;
248 QByteArray mainDeviceArray;
249 mainDeviceArray.setRawData(reinterpret_cast<const char *>(&mainDevice), sizeof(dev_t));
250 send_main_device(mainDeviceArray);
251
252 // At least one tranche required, we just send one with all formats
253 send_tranche_target_device(mainDeviceArray);
254 send_tranche_flags(0);
255 send_tranche_formats(indices);
256 send_tranche_done();
257
258 send_done();
259}
260
261LinuxDmabufParams::LinuxDmabufParams(LinuxDmabufClientBufferIntegration *clientBufferIntegration, wl_resource *resource)
263 , m_clientBufferIntegration(clientBufferIntegration)
264{
265}
266
268{
269 for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
270 if (it.value().fd != -1)
271 close(it.value().fd);
272 it.value().fd = -1;
273 }
274}
275
276bool LinuxDmabufParams::handleCreateParams(Resource *resource, int width, int height, uint format, uint flags)
277{
278 if (m_used) {
279 wl_resource_post_error(resource->handle,
280 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED,
281 "Params already used");
282 return false;
283 }
284
285 if (width <= 0 || height <= 0) {
286 wl_resource_post_error(resource->handle,
287 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS,
288 "Invalid dimensions in create request");
289 return false;
290 }
291
292 if (m_planes.isEmpty()) {
293 wl_resource_post_error(resource->handle,
294 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
295 "Cannot create a buffer with no planes");
296 return false;
297 }
298
299 // check for holes in plane sequence
300 auto planeIds = m_planes.keys();
301 std::sort(planeIds.begin(), planeIds.end());
302 for (int i = 0; i < planeIds.size(); ++i) {
303 if (uint(i) != planeIds[i]) {
304 wl_resource_post_error(resource->handle,
305 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
306 "No dmabuf parameters provided for plane %i", i);
307 return false;
308 }
309 }
310
311 // check for overflows
312 for (auto it = m_planes.constBegin(); it != m_planes.constEnd(); ++it) {
313 const auto planeId = it.key();
314 const auto plane = it.value();
315 if (static_cast<int64_t>(plane.offset) + plane.stride > UINT32_MAX) {
316 wl_resource_post_error(resource->handle,
317 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
318 "Size overflow for plane %i",
319 planeId);
320 return false;
321 }
322 if (planeId == 0 && static_cast<int64_t>(plane.offset) + plane.stride * static_cast<int64_t>(height) > UINT32_MAX) {
323 wl_resource_post_error(resource->handle,
324 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
325 "Size overflow for plane %i",
326 planeId);
327 return false;
328 }
329
330 // do not report an error as it might be caused by the kernel not supporting seeking on dmabuf
331 off_t size = lseek(plane.fd, 0, SEEK_END);
332 if (size == -1) {
333 qCDebug(qLcWaylandCompositorHardwareIntegration) << "Seeking is not supported";
334 continue;
335 }
336
337 if (static_cast<int64_t>(plane.offset) >= size) {
338 wl_resource_post_error(resource->handle,
339 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
340 "Invalid offset %i for plane %i",
341 plane.offset, planeId);
342 return false;
343 }
344
345 if (static_cast<int64_t>(plane.offset) + static_cast<int64_t>(plane.stride) > size) {
346 wl_resource_post_error(resource->handle,
347 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
348 "Invalid stride %i for plane %i",
349 plane.stride, planeId);
350 return false;
351 }
352
353 // only valid for first plane as other planes might be sub-sampled
354 if (planeId == 0 && plane.offset + static_cast<int64_t>(plane.stride) * height > size) {
355 wl_resource_post_error(resource->handle,
356 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
357 "Invalid buffer stride or height for plane %i", planeId);
358 return false;
359 }
360 }
361
362 m_size = QSize(width, height);
363 m_drmFormat = format;
364 m_flags = flags;
365 m_used = true;
366
367 return true;
368}
369
371{
372 wl_resource_destroy(resource->handle);
373}
374
376{
377 Q_UNUSED(resource);
378 delete this;
379}
380
381void LinuxDmabufParams::zwp_linux_buffer_params_v1_add(Resource *resource, int32_t fd, uint32_t plane_idx, uint32_t offset, uint32_t stride, uint32_t modifier_hi, uint32_t modifier_lo)
382{
383 const uint64_t modifiers = (static_cast<uint64_t>(modifier_hi) << 32) | modifier_lo;
384 if (plane_idx >= LinuxDmabufWlBuffer::MaxDmabufPlanes) {
385 wl_resource_post_error(resource->handle,
386 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX,
387 "Plane index %i is out of bounds", plane_idx);
388 }
389
390 if (m_planes.contains(plane_idx)) {
391 wl_resource_post_error(resource->handle,
392 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET,
393 "Plane already set");
394 }
395
396 Plane plane;
397 plane.fd = fd;
398 plane.modifiers = modifiers;
399 plane.offset = offset;
400 plane.stride = stride;
401 m_planes.insert(plane_idx, plane);
402}
403
404void LinuxDmabufParams::zwp_linux_buffer_params_v1_create(Resource *resource, int32_t width, int32_t height, uint32_t format, uint32_t flags)
405{
406 if (!handleCreateParams(resource, width, height, format, flags))
407 return;
408
409 auto *buffer = new LinuxDmabufWlBuffer(resource->client(), m_clientBufferIntegration);
410 buffer->m_size = m_size;
411 buffer->m_flags = m_flags;
412 buffer->m_drmFormat = m_drmFormat;
413 buffer->m_planesNumber = m_planes.size(); // it is checked before that planes are in consecutive sequence
414 for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
415 buffer->m_planes[it.key()] = it.value();
416 it.value().fd = -1; // ownership is moved
417 }
418
419 if (!m_clientBufferIntegration->importBuffer(buffer->resource()->handle, buffer)) {
420 send_failed(resource->handle);
421 } else {
422 send_created(resource->handle, buffer->resource()->handle);
423 }
424}
425
426void LinuxDmabufParams::zwp_linux_buffer_params_v1_create_immed(Resource *resource, uint32_t buffer_id, int32_t width, int32_t height, uint32_t format, uint32_t flags)
427{
428 if (!handleCreateParams(resource, width, height, format, flags))
429 return;
430
431 auto *buffer = new LinuxDmabufWlBuffer(resource->client(), m_clientBufferIntegration, buffer_id);
432 buffer->m_size = m_size;
433 buffer->m_flags = m_flags;
434 buffer->m_drmFormat = m_drmFormat;
435 buffer->m_planesNumber = m_planes.size(); // it is checked before that planes are in consecutive sequence
436 for (auto it = m_planes.begin(); it != m_planes.end(); ++it) {
437 buffer->m_planes[it.key()] = it.value();
438 it.value().fd = -1; // ownership is moved
439 }
440
441 if (!m_clientBufferIntegration->importBuffer(buffer->resource()->handle, buffer)) {
442 // for the 'create_immed' request, the implementation can decide
443 // how to handle the failure by an unknown cause; we decide
444 // to raise a fatal error at the client
445 wl_resource_post_error(resource->handle,
446 ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER,
447 "Import of the provided DMA buffer failed");
448 }
449 // note: create signal shall not be sent for the 'create_immed' request
450}
451
452LinuxDmabufWlBuffer::LinuxDmabufWlBuffer(::wl_client *client, LinuxDmabufClientBufferIntegration *clientBufferIntegration, uint id)
453 : wl_buffer(client, id, 1 /*version*/)
454 , m_clientBufferIntegration(clientBufferIntegration)
455{
456}
457
459{
460 if (resource())
461 m_clientBufferIntegration->removeBuffer(resource()->handle);
462 deleteTextures();
463}
464
465void LinuxDmabufWlBuffer::buffer_destroy(Resource *resource)
466{
467 m_clientBufferIntegration->removeBuffer(resource->handle);
468 wl_resource_destroy(resource->handle);
469}
470
471void LinuxDmabufWlBuffer::deleteTextures()
472{
473 QMutexLocker locker(&m_texturesLock);
474
475 for (uint32_t i = 0; i < m_planesNumber; ++i) {
476 if (m_textures[i] != nullptr) {
477 QtWayland::QWaylandTextureOrphanage::instance()->admitTexture(m_textures[i],
478 m_texturesContext[i]);
479 m_textures[i] = nullptr;
480 m_texturesContext[i] = nullptr;
481 QObject::disconnect(m_texturesAboutToBeDestroyedConnection[i]);
482 m_texturesAboutToBeDestroyedConnection[i] = QMetaObject::Connection();
483 }
484 if (m_eglImages[i] != EGL_NO_IMAGE_KHR) {
485 m_clientBufferIntegration->deleteImage(m_eglImages[i]);
486 m_eglImages[i] = EGL_NO_IMAGE_KHR;
487 }
488 if (m_planes[i].fd != -1)
489 close(m_planes[i].fd);
490 m_planes[i].fd = -1;
491 }
492 m_planesNumber = 0;
493}
494
495void LinuxDmabufWlBuffer::initImage(uint32_t plane, EGLImageKHR image)
496{
497 Q_ASSERT(plane < m_planesNumber);
498 Q_ASSERT(m_eglImages.at(plane) == EGL_NO_IMAGE_KHR);
499 m_eglImages[plane] = image;
500}
501
502void LinuxDmabufWlBuffer::initTexture(uint32_t plane, QOpenGLTexture *texture)
503{
504 QMutexLocker locker(&m_texturesLock);
505
506 Q_ASSERT(plane < m_planesNumber);
507 Q_ASSERT(m_textures.at(plane) == nullptr);
508 Q_ASSERT(QOpenGLContext::currentContext());
509 m_textures[plane] = texture;
510 m_texturesContext[plane] = QOpenGLContext::currentContext();
511
512 m_texturesAboutToBeDestroyedConnection[plane] =
513 QObject::connect(m_texturesContext[plane], &QOpenGLContext::aboutToBeDestroyed,
514 m_texturesContext[plane], [this, plane]() {
515
516 QMutexLocker locker(&this->m_texturesLock);
517
518 // See above lock - there is a chance that this has already been removed from m_textures[plane]!
519 // Furthermore, we can trust that all the rest (e.g. disconnect) has also been properly executed!
520 if (this->m_textures[plane] == nullptr)
521 return;
522
523 delete this->m_textures[plane];
524
525 qCDebug(qLcWaylandCompositorHardwareIntegration)
526 << Q_FUNC_INFO
527 << "texture deleted due to QOpenGLContext::aboutToBeDestroyed!"
528 << "Pointer (now dead) was:" << (void*)(this->m_textures[plane])
529 << " Associated context (about to die too) is: " << (void*)(this->m_texturesContext[plane]);
530
531 this->m_textures[plane] = nullptr;
532 this->m_texturesContext[plane] = nullptr;
533
534 QObject::disconnect(this->m_texturesAboutToBeDestroyedConnection[plane]);
535 this->m_texturesAboutToBeDestroyedConnection[plane] = QMetaObject::Connection();
536
537 }, Qt::DirectConnection);
538}
539
541{
542 // In most cases this is redundant, but for instance if a buffer has been created,
543 // but not committed and the client disconnects, it is vital
544 m_clientBufferIntegration->removeBuffer(resource->handle);
545}
546
547QT_END_NAMESPACE
bool importBuffer(wl_resource *resource, LinuxDmabufWlBuffer *linuxDmabufBuffer)
void zwp_linux_dmabuf_feedback_v1_bind_resource(Resource *resource) override
void zwp_linux_dmabuf_feedback_v1_destroy_resource(Resource *resource) override
~LinuxDmabufFeedback() override
void zwp_linux_dmabuf_feedback_v1_destroy(Resource *resource) override
void zwp_linux_buffer_params_v1_create_immed(Resource *resource, uint32_t buffer_id, int32_t width, int32_t height, uint32_t format, uint32_t flags) override
~LinuxDmabufParams() override
LinuxDmabufParams(LinuxDmabufClientBufferIntegration *clientBufferIntegration, wl_resource *resource)
void zwp_linux_buffer_params_v1_create(Resource *resource, int32_t width, int32_t height, uint32_t format, uint32_t flags) override
void zwp_linux_buffer_params_v1_destroy(Resource *resource) override
void zwp_linux_buffer_params_v1_add(Resource *resource, int32_t fd, uint32_t plane_idx, uint32_t offset, uint32_t stride, uint32_t modifier_hi, uint32_t modifier_lo) override
void zwp_linux_buffer_params_v1_destroy_resource(Resource *resource) override
void initTexture(uint32_t plane, QOpenGLTexture *texture)
void initImage(uint32_t plane, EGLImageKHR image)
void buffer_destroy(Resource *resource) override
~LinuxDmabufWlBuffer() override
LinuxDmabufWlBuffer(::wl_client *client, LinuxDmabufClientBufferIntegration *clientBufferIntegration, uint id=0)
static const uint32_t MaxDmabufPlanes
void buffer_destroy_resource(Resource *resource) override
void zwp_linux_dmabuf_v1_get_surface_feedback(Resource *resource, uint32_t id, struct ::wl_resource *surface) override
void setDrmDevice(const char *drmDevice)
void zwp_linux_dmabuf_v1_bind_resource(Resource *resource) override
void setSupportedModifiers(const QHash< uint32_t, QList< uint64_t > > &modifiers)
void zwp_linux_dmabuf_v1_create_params(Resource *resource, uint32_t params_id) override
void zwp_linux_dmabuf_v1_get_default_feedback(Resource *resource, uint32_t id) override
#define DRM_FORMAT_MOD_LINEAR
Definition linuxdmabuf.h:35
#define DRM_FORMAT_MOD_INVALID
Definition linuxdmabuf.h:38
int fd
Definition linuxdmabuf.h:60
uint32_t stride
Definition linuxdmabuf.h:62
uint64_t modifiers
Definition linuxdmabuf.h:63
uint32_t offset
Definition linuxdmabuf.h:61