File indexing completed on 2024-05-19 05:32:43

0001 /*
0002     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "config-kwin.h"
0008 
0009 #include "wayland/shmclientbuffer.h"
0010 #include "wayland/display.h"
0011 #include "wayland/shmclientbuffer_p.h"
0012 
0013 #include <drm_fourcc.h>
0014 #include <fcntl.h>
0015 #include <mutex>
0016 #include <signal.h>
0017 #include <sys/mman.h>
0018 #include <sys/stat.h>
0019 
0020 namespace KWin
0021 {
0022 
0023 static constexpr int s_version = 1;
0024 
0025 static constexpr uint32_t s_formats[] = {
0026     WL_SHM_FORMAT_ARGB8888,
0027     WL_SHM_FORMAT_XRGB8888,
0028 #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
0029     WL_SHM_FORMAT_ARGB2101010,
0030     WL_SHM_FORMAT_XRGB2101010,
0031     WL_SHM_FORMAT_ABGR2101010,
0032     WL_SHM_FORMAT_XBGR2101010,
0033     WL_SHM_FORMAT_ABGR16161616,
0034     WL_SHM_FORMAT_XBGR16161616,
0035 #endif
0036 };
0037 
0038 class ShmSigbusData
0039 {
0040 public:
0041     ShmPool *pool = nullptr;
0042     int accessCount = 0;
0043 };
0044 
0045 static thread_local ShmSigbusData sigbusData;
0046 static struct sigaction prevSigbusAction;
0047 
0048 static uint32_t shmFormatToDrmFormat(uint32_t shmFormat)
0049 {
0050     switch (shmFormat) {
0051     case WL_SHM_FORMAT_ARGB8888:
0052         return DRM_FORMAT_ARGB8888;
0053     case WL_SHM_FORMAT_XRGB8888:
0054         return DRM_FORMAT_XRGB8888;
0055     default:
0056         return shmFormat; // other wl_shm formats match the drm formats
0057     }
0058 }
0059 
0060 ShmPool::ShmPool(ShmClientBufferIntegration *integration, wl_client *client, int id, uint32_t version, FileDescriptor &&fd, MemoryMap &&mapping)
0061     : QtWaylandServer::wl_shm_pool(client, id, version)
0062     , integration(integration)
0063     , mapping(std::move(mapping))
0064     , fd(std::move(fd))
0065 {
0066 #if HAVE_MEMFD
0067     const int seals = fcntl(this->fd.get(), F_GET_SEALS);
0068     if (seals != -1) {
0069         struct stat statbuf;
0070         if ((seals & F_SEAL_SHRINK) && fstat(this->fd.get(), &statbuf) >= 0) {
0071             sigbusImpossible = statbuf.st_size >= this->mapping.size();
0072         }
0073     }
0074 #endif
0075 }
0076 
0077 void ShmPool::ref()
0078 {
0079     ++refCount;
0080 }
0081 
0082 void ShmPool::unref()
0083 {
0084     --refCount;
0085     if (refCount == 0) {
0086         delete this;
0087     }
0088 }
0089 
0090 void ShmPool::shm_pool_destroy_resource(Resource *resource)
0091 {
0092     unref();
0093 }
0094 
0095 void ShmPool::shm_pool_destroy(Resource *resource)
0096 {
0097     wl_resource_destroy(resource->handle);
0098 }
0099 
0100 void ShmPool::shm_pool_create_buffer(Resource *resource, uint32_t id, int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format)
0101 {
0102     if (std::find(std::begin(s_formats), std::end(s_formats), format) == std::end(s_formats)) {
0103         wl_resource_post_error(resource->handle,
0104                                WL_SHM_ERROR_INVALID_FORMAT,
0105                                "invalid format 0x%x",
0106                                format);
0107         return;
0108     }
0109 
0110     if (offset < 0 || width <= 0 || height <= 0 || stride < width
0111         || INT32_MAX / stride < height || offset > mapping.size() - stride * height) {
0112         wl_resource_post_error(resource->handle,
0113                                WL_SHM_ERROR_INVALID_STRIDE,
0114                                "invalid width, height or stride (%dx%d, %u)",
0115                                width, height, stride);
0116         return;
0117     }
0118 
0119     ShmAttributes attributes{
0120         .fd = fd.duplicate(),
0121         .stride = stride,
0122         .offset = offset,
0123         .size = QSize(width, height),
0124         .format = shmFormatToDrmFormat(format),
0125     };
0126 
0127     new ShmClientBuffer(this, std::move(attributes), resource->client(), id);
0128 }
0129 
0130 void ShmPool::shm_pool_resize(Resource *resource, int32_t size)
0131 {
0132     if (size < mapping.size()) {
0133         wl_resource_post_error(resource->handle, WL_SHM_ERROR_INVALID_FD, "shrinking pool invalid");
0134         return;
0135     }
0136 
0137     auto remapping = MemoryMap(size, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0);
0138     if (remapping.isValid()) {
0139         mapping = std::move(remapping);
0140     } else {
0141         wl_resource_post_error(resource->handle, WL_SHM_ERROR_INVALID_FD, "failed to map shm pool with the new size");
0142     }
0143 }
0144 
0145 void ShmClientBuffer::buffer_destroy_resource(wl_resource *resource)
0146 {
0147     if (ShmClientBuffer *buffer = ShmClientBuffer::get(resource)) {
0148         buffer->m_resource = nullptr;
0149         buffer->drop();
0150     }
0151 }
0152 
0153 void ShmClientBuffer::buffer_destroy(wl_client *client, wl_resource *resource)
0154 {
0155     wl_resource_destroy(resource);
0156 }
0157 
0158 const struct wl_buffer_interface ShmClientBuffer::implementation = {
0159     .destroy = buffer_destroy,
0160 };
0161 
0162 ShmClientBuffer::ShmClientBuffer(ShmPool *pool, ShmAttributes attributes, wl_client *client, uint32_t id)
0163     : m_shmPool(pool)
0164     , m_shmAttributes(std::move(attributes))
0165 {
0166     m_shmPool->ref();
0167 
0168     connect(this, &GraphicsBuffer::released, [this]() {
0169         wl_buffer_send_release(m_resource);
0170     });
0171 
0172     m_resource = wl_resource_create(client, &wl_buffer_interface, 1, id);
0173     wl_resource_set_implementation(m_resource, &implementation, this, buffer_destroy_resource);
0174 }
0175 
0176 ShmClientBuffer::~ShmClientBuffer()
0177 {
0178     m_shmPool->unref();
0179 }
0180 
0181 QSize ShmClientBuffer::size() const
0182 {
0183     return m_shmAttributes.size;
0184 }
0185 
0186 bool ShmClientBuffer::hasAlphaChannel() const
0187 {
0188     return alphaChannelFromDrmFormat(m_shmAttributes.format);
0189 }
0190 
0191 const ShmAttributes *ShmClientBuffer::shmAttributes() const
0192 {
0193     return &m_shmAttributes;
0194 }
0195 
0196 ShmClientBuffer *ShmClientBuffer::get(wl_resource *resource)
0197 {
0198     if (wl_resource_instance_of(resource, &wl_buffer_interface, &implementation)) {
0199         return static_cast<ShmClientBuffer *>(wl_resource_get_user_data(resource));
0200     }
0201     return nullptr;
0202 }
0203 
0204 static void sigbusHandler(int signum, siginfo_t *info, void *context)
0205 {
0206     auto reraise = [&]() {
0207         if (prevSigbusAction.sa_flags & SA_SIGINFO) {
0208             prevSigbusAction.sa_sigaction(signum, info, context);
0209         } else {
0210             prevSigbusAction.sa_handler(signum);
0211         }
0212     };
0213 
0214     const ShmPool *pool = sigbusData.pool;
0215     if (!pool) {
0216         reraise();
0217         return;
0218     }
0219 
0220     const uchar *addr = static_cast<uchar *>(info->si_addr);
0221     const uchar *mappingStart = static_cast<uchar *>(pool->mapping.data());
0222     if (addr < mappingStart || addr >= mappingStart + pool->mapping.size()) {
0223         reraise();
0224         return;
0225     }
0226 
0227     // Replace the faulty mapping with a new one that's filled with zeros.
0228     if (mmap(pool->mapping.data(), pool->mapping.size(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == MAP_FAILED) {
0229         reraise();
0230         return;
0231     }
0232 }
0233 
0234 GraphicsBuffer::Map ShmClientBuffer::map(MapFlags flags)
0235 {
0236     if (!m_shmPool->sigbusImpossible) {
0237         // A SIGBUS signal may be emitted if the backing file is shrinked and we access now
0238         // removed pages. Install a signal handler to handle this case. Note that if the
0239         // backing file has F_SEAL_SHRINK seal, then we don't need to do anything.
0240 
0241         static std::once_flag sigbusOnce;
0242         std::call_once(sigbusOnce, []() {
0243             struct sigaction action;
0244             memset(&action, 0, sizeof(action));
0245             sigemptyset(&action.sa_mask);
0246             action.sa_sigaction = sigbusHandler;
0247             action.sa_flags = SA_SIGINFO | SA_NODEFER;
0248             sigaction(SIGBUS, &action, &prevSigbusAction);
0249         });
0250 
0251         Q_ASSERT(!sigbusData.pool || sigbusData.pool == m_shmPool);
0252         sigbusData.pool = m_shmPool;
0253         ++sigbusData.accessCount;
0254     }
0255 
0256     return Map{
0257         .data = reinterpret_cast<uchar *>(m_shmPool->mapping.data()) + m_shmAttributes.offset,
0258         .stride = uint32_t(m_shmAttributes.stride),
0259     };
0260 }
0261 
0262 void ShmClientBuffer::unmap()
0263 {
0264     if (m_shmPool->sigbusImpossible) {
0265         return;
0266     }
0267 
0268     Q_ASSERT(sigbusData.accessCount > 0);
0269     --sigbusData.accessCount;
0270     if (sigbusData.accessCount == 0) {
0271         sigbusData.pool = nullptr;
0272     }
0273 }
0274 
0275 ShmClientBufferIntegrationPrivate::ShmClientBufferIntegrationPrivate(Display *display, ShmClientBufferIntegration *q)
0276     : QtWaylandServer::wl_shm(*display, s_version)
0277     , q(q)
0278 {
0279 }
0280 
0281 void ShmClientBufferIntegrationPrivate::shm_bind_resource(Resource *resource)
0282 {
0283     for (const uint32_t &format : s_formats) {
0284         send_format(resource->handle, format);
0285     }
0286 }
0287 
0288 void ShmClientBufferIntegrationPrivate::shm_create_pool(Resource *resource, uint32_t id, int32_t fd, int32_t size)
0289 {
0290     FileDescriptor fileDescriptor{fd};
0291 
0292     if (size <= 0) {
0293         wl_resource_post_error(resource->handle, error_invalid_stride, "invalid size (%d)", size);
0294         return;
0295     }
0296 
0297     auto mapping = MemoryMap(size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
0298     if (!mapping.isValid()) {
0299         wl_resource_post_error(resource->handle, error_invalid_fd, "failed to map shm pool");
0300         return;
0301     }
0302 
0303     new ShmPool(q, resource->client(), id, resource->version(), std::move(fileDescriptor), std::move(mapping));
0304 }
0305 
0306 ShmClientBufferIntegration::ShmClientBufferIntegration(Display *display)
0307     : QObject(display)
0308     , d(new ShmClientBufferIntegrationPrivate(display, this))
0309 {
0310 }
0311 
0312 ShmClientBufferIntegration::~ShmClientBufferIntegration()
0313 {
0314 }
0315 
0316 } // namespace KWin
0317 
0318 #include "moc_shmclientbuffer_p.cpp"
0319 
0320 #include "moc_shmclientbuffer.cpp"