| // 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 <errno.h> | |
| #include <pthread.h> | |
| #include <string.h> | |
| #include <sys/mman.h> | |
| // 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<SharedSemaphore *>(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_ |