// Copyright 2019 The SwiftShader Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef VK_SEMAPHORE_EXTERNAL_LINUX_H_ #define VK_SEMAPHORE_EXTERNAL_LINUX_H_ #include "System/Linux/MemFd.hpp" #include "System/Memory.hpp" #include #include #include #include // An external semaphore implementation for Linux, that uses memfd-backed // shared memory regions as the underlying implementation. The region contains // a single SharedSemaphore instance, which is a reference-counted semaphore // implementation based on a pthread process-shared mutex + condition variable // pair. // // This implementation works on any Linux system with at least kernel 3.17 // (which should be sufficient for any not-so-recent Android system) and doesn't // require special libraries installed on the system. // // NOTE: This is not interoperable with other Linux ICDs that use Linux kernel // sync file objects (which correspond to // VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) instead. // // A process-shared semaphore implementation that can be stored in // a process-shared memory region. It also includes a reference count to // ensure it is only destroyed when the last reference to it is dropped. class SharedSemaphore { public: SharedSemaphore(bool initialValue) : signaled(initialValue) { pthread_mutexattr_t mattr; pthread_mutexattr_init(&mattr); pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); pthread_mutex_init(&mutex, &mattr); pthread_mutexattr_destroy(&mattr); pthread_condattr_t cattr; pthread_condattr_init(&cattr); pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); pthread_cond_init(&cond, &cattr); pthread_condattr_destroy(&cattr); } ~SharedSemaphore() { pthread_cond_destroy(&cond); pthread_mutex_destroy(&mutex); } // Increment reference count. void addRef() { pthread_mutex_lock(&mutex); ref_count++; pthread_mutex_unlock(&mutex); } // Decrement reference count and returns true iff it reaches 0. bool deref() { pthread_mutex_lock(&mutex); bool result = (--ref_count == 0); pthread_mutex_unlock(&mutex); return result; } void wait() { pthread_mutex_lock(&mutex); while(!signaled) { pthread_cond_wait(&cond, &mutex); } // From Vulkan 1.1.119 spec, section 6.4.2: // Unlike fences or events, the act of waiting for a semaphore also // unsignals that semaphore. signaled = false; pthread_mutex_unlock(&mutex); } // Just like wait() but never blocks. Returns true if the semaphore // was signaled (and reset by the function), or false otherwise. // Used to avoid using a background thread for waiting in the case // where the semaphore is already signaled. bool tryWait() { pthread_mutex_lock(&mutex); bool result = signaled; if(result) { signaled = false; } pthread_mutex_unlock(&mutex); return result; } void signal() { pthread_mutex_lock(&mutex); signaled = true; pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mutex); } private: pthread_mutex_t mutex; pthread_cond_t cond; int ref_count = 1; bool signaled = false; }; namespace vk { class OpaqueFdExternalSemaphore : public BinarySemaphore::External { public: ~OpaqueFdExternalSemaphore() { unmapRegion(); } // Initialize instance by creating a new shared memory region. VkResult init(bool initialState) override { // Allocate or import the region's file descriptor. const size_t size = sw::memoryPageSize(); // To be exportable, the PosixSemaphore must be stored in a shared // memory region. static int counter = 0; char name[40]; snprintf(name, sizeof(name), "SwiftShader.Semaphore.%d", ++counter); if(!memfd.allocate(name, size)) { TRACE("memfd.allocate() returned %s", strerror(errno)); return VK_ERROR_INITIALIZATION_FAILED; } if(!mapRegion(size, true, initialState)) return VK_ERROR_INITIALIZATION_FAILED; return VK_SUCCESS; } // Import an existing semaphore through its file descriptor. VkResult importOpaqueFd(int fd) override { unmapRegion(); memfd.importFd(fd); if(!mapRegion(sw::memoryPageSize(), false, false)) return VK_ERROR_INITIALIZATION_FAILED; return VK_SUCCESS; } // Export the current semaphore as a duplicated file descriptor to the same // region. This can be consumed by importFd() running in a different // process. VkResult exportOpaqueFd(int *pFd) override { int fd = memfd.exportFd(); if(fd < 0) { return VK_ERROR_INVALID_EXTERNAL_HANDLE; } *pFd = fd; return VK_SUCCESS; } void wait() override { semaphore->wait(); } bool tryWait() override { return semaphore->tryWait(); } void signal() override { semaphore->signal(); } private: void unmapRegion() { if(semaphore) { if(semaphore->deref()) { semaphore->~SharedSemaphore(); } memfd.unmap(semaphore, sw::memoryPageSize()); memfd.close(); semaphore = nullptr; } } // Remap the shared region and setup the semaphore or increment its reference count. bool mapRegion(size_t size, bool needsInitialization, bool initialValue) { // Map the region into memory and point the semaphore to it. void *addr = memfd.mapReadWrite(0, size); if(!addr) { TRACE("mmap() failed: %s", strerror(errno)); return false; } semaphore = reinterpret_cast(addr); if(needsInitialization) { new(semaphore) SharedSemaphore(initialValue); } else { semaphore->addRef(); } return true; } LinuxMemFd memfd; SharedSemaphore *semaphore = nullptr; }; } // namespace vk #endif // VK_SEMAPHORE_EXTERNAL_LINUX_H_