blob: d4e4cd2d229272d6dfc5392c11779a9f6ea5e7a7 [file] [log] [blame]
// 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 Semaphore::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_