File indexing completed on 2024-11-10 04:57:33
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"