Addition of Conditional Variables

This commit adds working conditional variables, in addition to the mutex and threading implementation. It directly depends on the memory optimization from the previous commit to be able to perform atomic operations on the mutex.
This commit is contained in:
◱ PixelyIon 2020-02-05 12:12:53 +05:30 committed by ◱ PixelyIon
parent 03b65bd90a
commit 694c8ef4e2
4 changed files with 140 additions and 84 deletions

View File

@ -43,7 +43,7 @@ namespace skyline {
constexpr u64 MaxSyncHandles = 0x40; //!< The total amount of handles that can be passed to WaitSynchronization constexpr u64 MaxSyncHandles = 0x40; //!< The total amount of handles that can be passed to WaitSynchronization
constexpr handle_t BaseHandleIndex = 0xD000; // The index of the base handle constexpr handle_t BaseHandleIndex = 0xD000; // The index of the base handle
constexpr handle_t ThreadSelf = 0xFFFF8000; //!< This is the handle used by threads to refer to themselves constexpr handle_t ThreadSelf = 0xFFFF8000; //!< This is the handle used by threads to refer to themselves
constexpr u8 DefaultPriority = 31; //!< The default priority of a process constexpr u8 DefaultPriority = 44; //!< The default priority of a process
constexpr std::pair<int8_t, int8_t> PriorityAn = {19, -8}; //!< The range of priority for Android, taken from https://medium.com/mindorks/exploring-android-thread-priority-5d0542eebbd1 constexpr std::pair<int8_t, int8_t> PriorityAn = {19, -8}; //!< The range of priority for Android, taken from https://medium.com/mindorks/exploring-android-thread-priority-5d0542eebbd1
constexpr std::pair<u8, u8> PriorityNin = {0, 63}; //!< The range of priority for the Nintendo Switch constexpr std::pair<u8, u8> PriorityNin = {0, 63}; //!< The range of priority for the Nintendo Switch
constexpr u32 MtxOwnerMask = 0xBFFFFFFF; //!< The mask of values which contain the owner of a mutex constexpr u32 MtxOwnerMask = 0xBFFFFFFF; //!< The mask of values which contain the owner of a mutex

View File

@ -10,7 +10,7 @@ namespace skyline::kernel::svc {
state.logger->Warn("svcSetHeapSize: 'size' not divisible by 2MB: {}", size); state.logger->Warn("svcSetHeapSize: 'size' not divisible by 2MB: {}", size);
return; return;
} }
auto& heap = state.process->heap; auto &heap = state.process->heap;
heap->Resize(size); heap->Resize(size);
state.ctx->registers.w0 = constant::status::Success; state.ctx->registers.w0 = constant::status::Success;
state.ctx->registers.x1 = heap->address; state.ctx->registers.x1 = heap->address;
@ -45,7 +45,7 @@ namespace skyline::kernel::svc {
state.logger->Warn("svcSetMemoryAttribute: Cannot find memory region: 0x{:X}", address); state.logger->Warn("svcSetMemoryAttribute: Cannot find memory region: 0x{:X}", address);
return; return;
} }
if(!chunk->state.AttributeChangeAllowed) { if (!chunk->state.AttributeChangeAllowed) {
state.ctx->registers.w0 = constant::status::InvState; state.ctx->registers.w0 = constant::status::InvState;
state.logger->Warn("svcSetMemoryAttribute: Attribute change not allowed for chunk: 0x{:X}", address); state.logger->Warn("svcSetMemoryAttribute: Attribute change not allowed for chunk: 0x{:X}", address);
return; return;
@ -60,29 +60,29 @@ namespace skyline::kernel::svc {
const u64 destination = state.ctx->registers.x0; const u64 destination = state.ctx->registers.x0;
const u64 source = state.ctx->registers.x1; const u64 source = state.ctx->registers.x1;
const u64 size = state.ctx->registers.x2; const u64 size = state.ctx->registers.x2;
if(!utils::PageAligned(destination) || !utils::PageAligned(source)) { if (!utils::PageAligned(destination) || !utils::PageAligned(source)) {
state.ctx->registers.w0 = constant::status::InvAddress; state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcMapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size); state.logger->Warn("svcMapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return; return;
} }
if(!utils::PageAligned(size)) { if (!utils::PageAligned(size)) {
state.ctx->registers.w0 = constant::status::InvSize; state.ctx->registers.w0 = constant::status::InvSize;
state.logger->Warn("svcMapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size); state.logger->Warn("svcMapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return; return;
} }
auto stack = state.os->memory.GetRegion(memory::Regions::Stack); auto stack = state.os->memory.GetRegion(memory::Regions::Stack);
if(!stack.IsInside(destination)) { if (!stack.IsInside(destination)) {
state.ctx->registers.w0 = constant::status::InvMemRange; state.ctx->registers.w0 = constant::status::InvMemRange;
state.logger->Warn("svcMapMemory: Destination not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size); state.logger->Warn("svcMapMemory: Destination not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return; return;
} }
auto descriptor = state.os->memory.Get(source); auto descriptor = state.os->memory.Get(source);
if(!descriptor) { if (!descriptor) {
state.ctx->registers.w0 = constant::status::InvAddress; state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcMapMemory: Source has no descriptor: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size); state.logger->Warn("svcMapMemory: Source has no descriptor: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return; return;
} }
if(!descriptor->chunk.state.MapAllowed) { if (!descriptor->chunk.state.MapAllowed) {
state.ctx->registers.w0 = constant::status::InvState; state.ctx->registers.w0 = constant::status::InvState;
state.logger->Warn("svcMapMemory: Source doesn't allow usage of svcMapMemory: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes) 0x{:X}", source, destination, size, descriptor->chunk.state.value); state.logger->Warn("svcMapMemory: Source doesn't allow usage of svcMapMemory: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes) 0x{:X}", source, destination, size, descriptor->chunk.state.value);
return; return;
@ -90,7 +90,7 @@ namespace skyline::kernel::svc {
state.process->NewHandle<type::KPrivateMemory>(destination, size, descriptor->block.permission, memory::MemoryStates::Stack); state.process->NewHandle<type::KPrivateMemory>(destination, size, descriptor->block.permission, memory::MemoryStates::Stack);
state.process->CopyMemory(source, destination, size); state.process->CopyMemory(source, destination, size);
auto object = state.process->GetMemoryObject(source); auto object = state.process->GetMemoryObject(source);
if(!object) if (!object)
throw exception("svcMapMemory: Cannot find memory object in handle table for address 0x{:X}", source); throw exception("svcMapMemory: Cannot find memory object in handle table for address 0x{:X}", source);
object->item->UpdatePermission(source, size, {false, false, false}); object->item->UpdatePermission(source, size, {false, false, false});
state.logger->Debug("svcMapMemory: Mapped range 0x{:X} - 0x{:X} to 0x{:X} - 0x{:X} (Size: 0x{:X} bytes)", source, source + size, destination, destination + size, size); state.logger->Debug("svcMapMemory: Mapped range 0x{:X} - 0x{:X} to 0x{:X} - 0x{:X} (Size: 0x{:X} bytes)", source, source + size, destination, destination + size, size);
@ -101,41 +101,41 @@ namespace skyline::kernel::svc {
const u64 source = state.ctx->registers.x0; const u64 source = state.ctx->registers.x0;
const u64 destination = state.ctx->registers.x1; const u64 destination = state.ctx->registers.x1;
const u64 size = state.ctx->registers.x2; const u64 size = state.ctx->registers.x2;
if(!utils::PageAligned(destination) || !utils::PageAligned(source)) { if (!utils::PageAligned(destination) || !utils::PageAligned(source)) {
state.ctx->registers.w0 = constant::status::InvAddress; state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcUnmapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size); state.logger->Warn("svcUnmapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return; return;
} }
if(!utils::PageAligned(size)) { if (!utils::PageAligned(size)) {
state.ctx->registers.w0 = constant::status::InvSize; state.ctx->registers.w0 = constant::status::InvSize;
state.logger->Warn("svcUnmapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size); state.logger->Warn("svcUnmapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size);
return; return;
} }
auto stack = state.os->memory.GetRegion(memory::Regions::Stack); auto stack = state.os->memory.GetRegion(memory::Regions::Stack);
if(!stack.IsInside(source)) { if (!stack.IsInside(source)) {
state.ctx->registers.w0 = constant::status::InvMemRange; state.ctx->registers.w0 = constant::status::InvMemRange;
state.logger->Warn("svcUnmapMemory: Source not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size); state.logger->Warn("svcUnmapMemory: Source not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return; return;
} }
auto sourceDesc = state.os->memory.Get(source); auto sourceDesc = state.os->memory.Get(source);
auto destDesc = state.os->memory.Get(destination); auto destDesc = state.os->memory.Get(destination);
if(!sourceDesc || !destDesc) { if (!sourceDesc || !destDesc) {
state.ctx->registers.w0 = constant::status::InvAddress; state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcUnmapMemory: Addresses have no descriptor: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size); state.logger->Warn("svcUnmapMemory: Addresses have no descriptor: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size);
return; return;
} }
if(!destDesc->chunk.state.MapAllowed) { if (!destDesc->chunk.state.MapAllowed) {
state.ctx->registers.w0 = constant::status::InvState; state.ctx->registers.w0 = constant::status::InvState;
state.logger->Warn("svcUnmapMemory: Destination doesn't allow usage of svcMapMemory: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes) 0x{:X}", source, destination, size, destDesc->chunk.state.value); state.logger->Warn("svcUnmapMemory: Destination doesn't allow usage of svcMapMemory: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes) 0x{:X}", source, destination, size, destDesc->chunk.state.value);
return; return;
} }
auto destObject = state.process->GetMemoryObject(destination); auto destObject = state.process->GetMemoryObject(destination);
if(!destObject) if (!destObject)
throw exception("svcUnmapMemory: Cannot find destination memory object in handle table for address 0x{:X}", destination); throw exception("svcUnmapMemory: Cannot find destination memory object in handle table for address 0x{:X}", destination);
destObject->item->UpdatePermission(destination, size, sourceDesc->block.permission); destObject->item->UpdatePermission(destination, size, sourceDesc->block.permission);
state.process->CopyMemory(destination, source, size); state.process->CopyMemory(destination, source, size);
auto sourceObject = state.process->GetMemoryObject(destination); auto sourceObject = state.process->GetMemoryObject(destination);
if(!sourceObject) if (!sourceObject)
throw exception("svcUnmapMemory: Cannot find source memory object in handle table for address 0x{:X}", source); throw exception("svcUnmapMemory: Cannot find source memory object in handle table for address 0x{:X}", source);
state.process->DeleteHandle(sourceObject->handle); state.process->DeleteHandle(sourceObject->handle);
state.logger->Debug("svcUnmapMemory: Unmapped range 0x{:X} - 0x{:X} to 0x{:X} - 0x{:X} (Size: 0x{:X} bytes)", source, source + size, destination, destination + size, size); state.logger->Debug("svcUnmapMemory: Unmapped range 0x{:X} - 0x{:X} to 0x{:X} - 0x{:X} (Size: 0x{:X} bytes)", source, source + size, destination, destination + size, size);
@ -379,7 +379,7 @@ namespace skyline::kernel::svc {
state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout); state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout);
auto start = utils::GetCurrTimeNs(); auto start = utils::GetCurrTimeNs();
while (true) { while (true) {
if(state.thread->cancelSync) { if (state.thread->cancelSync) {
state.thread->cancelSync = false; state.thread->cancelSync = false;
state.ctx->registers.w0 = constant::status::Interrupted; state.ctx->registers.w0 = constant::status::Interrupted;
break; break;
@ -414,62 +414,63 @@ namespace skyline::kernel::svc {
void ArbitrateLock(DeviceState &state) { void ArbitrateLock(DeviceState &state) {
auto address = state.ctx->registers.x1; auto address = state.ctx->registers.x1;
if (!utils::WordAligned(address)) { if (!utils::WordAligned(address)) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcArbitrateLock: 'address' not word aligned: 0x{:X}", address); state.logger->Warn("svcArbitrateLock: 'address' not word aligned: 0x{:X}", address);
state.ctx->registers.w0 = constant::status::InvAddress;
return; return;
} }
auto ownerHandle = state.ctx->registers.w0; auto ownerHandle = state.ctx->registers.w0;
auto requesterHandle = state.ctx->registers.w2; auto requesterHandle = state.ctx->registers.w2;
if (requesterHandle != state.thread->handle) if (requesterHandle != state.thread->handle)
throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", requesterHandle, state.thread->handle); throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", requesterHandle, state.thread->handle);
state.logger->Debug("svcArbitrateLock: Locking mutex at 0x{:X} for thread {}", address, state.thread->pid); state.logger->Debug("svcArbitrateLock: Locking mutex at 0x{:X}", address);
state.process->MutexLock(address, ownerHandle); if (state.process->MutexLock(address, ownerHandle))
state.logger->Debug("svcArbitrateLock: Locked mutex at 0x{:X} for thread {}", address, state.thread->pid); state.logger->Debug("svcArbitrateLock: Locked mutex at 0x{:X}", address);
else
state.logger->Debug("svcArbitrateLock: Owner handle did not match current owner for mutex at 0x{:X}", address);
state.ctx->registers.w0 = constant::status::Success; state.ctx->registers.w0 = constant::status::Success;
} }
void ArbitrateUnlock(DeviceState &state) { void ArbitrateUnlock(DeviceState &state) {
auto address = state.ctx->registers.x0; auto address = state.ctx->registers.x0;
if (!utils::WordAligned(address)) { if (!utils::WordAligned(address)) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcArbitrateUnlock: 'address' not word aligned: 0x{:X}", address); state.logger->Warn("svcArbitrateUnlock: 'address' not word aligned: 0x{:X}", address);
state.ctx->registers.w0 = constant::status::InvAddress;
return; return;
} }
state.logger->Debug("svcArbitrateUnlock: Unlocking mutex at 0x{:X}", address); state.logger->Debug("svcArbitrateUnlock: Unlocking mutex at 0x{:X}", address);
if(state.process->MutexUnlock(address)) { if (state.process->MutexUnlock(address)) {
state.ctx->registers.w0 = constant::status::Success;
state.logger->Debug("svcArbitrateUnlock: Unlocked mutex at 0x{:X}", address); state.logger->Debug("svcArbitrateUnlock: Unlocked mutex at 0x{:X}", address);
state.ctx->registers.w0 = constant::status::Success;
} else { } else {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Debug("svcArbitrateUnlock: A non-owner thread tried to release a mutex at 0x{:X}", address); state.logger->Debug("svcArbitrateUnlock: A non-owner thread tried to release a mutex at 0x{:X}", address);
state.ctx->registers.w0 = constant::status::InvAddress;
} }
} }
void WaitProcessWideKeyAtomic(DeviceState &state) { void WaitProcessWideKeyAtomic(DeviceState &state) {
auto mtxAddress = state.ctx->registers.x0; auto mtxAddress = state.ctx->registers.x0;
if (!utils::WordAligned(mtxAddress)) { if (!utils::WordAligned(mtxAddress)) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex address not word aligned: 0x{:X}", mtxAddress); state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex address not word aligned: 0x{:X}", mtxAddress);
state.ctx->registers.w0 = constant::status::InvAddress;
return; return;
} }
auto condAddress = state.ctx->registers.x1; auto condAddress = state.ctx->registers.x1;
auto handle = state.ctx->registers.w2; auto handle = state.ctx->registers.w2;
if (handle != state.thread->handle) if (handle != state.thread->handle)
throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", handle, state.thread->handle); throw exception("svcWaitProcessWideKeyAtomic: Handle doesn't match current thread: 0x{:X} for thread 0x{:X}", handle, state.thread->handle);
if(!state.process->MutexUnlock(mtxAddress)) { if (!state.process->MutexUnlock(mtxAddress)) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Debug("WaitProcessWideKeyAtomic: A non-owner thread tried to release a mutex at 0x{:X}", mtxAddress); state.logger->Debug("WaitProcessWideKeyAtomic: A non-owner thread tried to release a mutex at 0x{:X}", mtxAddress);
state.ctx->registers.w0 = constant::status::InvAddress;
return; return;
} }
auto timeout = state.ctx->registers.x3; auto timeout = state.ctx->registers.x3;
state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x{:X}, Timeout: {} ns", mtxAddress, condAddress, timeout); state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x{:X}, Timeout: {} ns", mtxAddress, condAddress, timeout);
if (state.process->ConditionalVariableWait(condAddress, timeout)) { if (state.process->ConditionalVariableWait(condAddress, mtxAddress, timeout)) {
state.ctx->registers.w0 = constant::status::Success;
state.process->MutexLock(mtxAddress, handle, true);
state.logger->Debug("svcWaitProcessWideKeyAtomic: Waited for conditional variable and relocked mutex"); state.logger->Debug("svcWaitProcessWideKeyAtomic: Waited for conditional variable and relocked mutex");
state.ctx->registers.w0 = constant::status::Success;
} else { } else {
state.ctx->registers.w0 = constant::status::Timeout;
state.logger->Debug("svcWaitProcessWideKeyAtomic: Wait has timed out"); state.logger->Debug("svcWaitProcessWideKeyAtomic: Wait has timed out");
state.ctx->registers.w0 = constant::status::Timeout;
} }
} }

View File

@ -109,7 +109,7 @@ namespace skyline::kernel::type {
} }
void KProcess::WriteMemory(void *source, const u64 offset, const size_t size, const bool forceGuest) const { void KProcess::WriteMemory(void *source, const u64 offset, const size_t size, const bool forceGuest) const {
if(!forceGuest) { if (!forceGuest) {
auto destination = GetHostAddress(offset); auto destination = GetHostAddress(offset);
if (destination) { if (destination) {
memcpy(reinterpret_cast<void *>(destination), source, size); memcpy(reinterpret_cast<void *>(destination), source, size);
@ -131,7 +131,7 @@ namespace skyline::kernel::type {
void KProcess::CopyMemory(u64 source, u64 destination, size_t size) const { void KProcess::CopyMemory(u64 source, u64 destination, size_t size) const {
auto sourceHost = GetHostAddress(source); auto sourceHost = GetHostAddress(source);
auto destinationHost = GetHostAddress(destination); auto destinationHost = GetHostAddress(destination);
if(sourceHost && destinationHost) { if (sourceHost && destinationHost) {
memcpy(reinterpret_cast<void *>(destinationHost), reinterpret_cast<const void *>(sourceHost), size); memcpy(reinterpret_cast<void *>(destinationHost), reinterpret_cast<const void *>(sourceHost), size);
} else { } else {
if (size <= PAGE_SIZE) { if (size <= PAGE_SIZE) {
@ -165,65 +165,66 @@ namespace skyline::kernel::type {
return std::nullopt; return std::nullopt;
} }
void KProcess::MutexLock(u64 address, handle_t owner, bool alwaysLock) { bool KProcess::MutexLock(u64 address, handle_t owner) {
std::unique_lock lock(mutexLock); std::unique_lock lock(mutexLock);
u32 mtxVal = ReadMemory<u32>(address); auto mtx = GetPointer<u32>(address);
if (alwaysLock) {
if (!mtxVal) {
state.logger->Warn("Mutex Value was 0");
mtxVal = (constant::MtxOwnerMask & state.thread->handle);
WriteMemory<u32>(mtxVal, address);
return;
// TODO: Replace with atomic CAS
}
} else {
if (mtxVal != (owner | ~constant::MtxOwnerMask))
return;
}
auto &mtxWaiters = mutexes[address]; auto &mtxWaiters = mutexes[address];
u32 mtxExpected = 0;
if (__atomic_compare_exchange_n(mtx, &mtxExpected, (constant::MtxOwnerMask & state.thread->handle) | (mtxWaiters.empty() ? 0 : ~constant::MtxOwnerMask), false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
return true;
if (owner && (__atomic_load_n(mtx, __ATOMIC_SEQ_CST) != (owner | ~constant::MtxOwnerMask)))
return false;
std::shared_ptr<WaitStatus> status; std::shared_ptr<WaitStatus> status;
for (auto it = mtxWaiters.begin();; ++it) { for (auto it = mtxWaiters.begin();; ++it) {
if (it != mtxWaiters.end() && (*it)->priority >= state.thread->priority) if (it != mtxWaiters.end() && (*it)->priority >= state.thread->priority)
continue; continue;
status = std::make_shared<WaitStatus>(state.thread->priority, state.thread->pid); status = std::make_shared<WaitStatus>(state.thread->priority, state.thread->handle);
mtxWaiters.insert(it, status); mtxWaiters.insert(it, status);
break; break;
} }
lock.unlock(); lock.unlock();
while (!status->flag); while (!status->flag);
lock.lock(); lock.lock();
status->flag = false;
for (auto it = mtxWaiters.begin(); it != mtxWaiters.end(); ++it) for (auto it = mtxWaiters.begin(); it != mtxWaiters.end(); ++it)
if ((*it)->pid == state.thread->pid) { if ((*it)->handle == state.thread->handle) {
mtxWaiters.erase(it); mtxWaiters.erase(it);
break; break;
} }
mtxVal = (constant::MtxOwnerMask & state.thread->handle) | (mtxWaiters.empty() ? 0 : ~constant::MtxOwnerMask);
WriteMemory<u32>(mtxVal, address);
lock.unlock();
}
bool KProcess::MutexUnlock(u64 address) {
std::lock_guard lock(mutexLock);
u32 mtxVal = ReadMemory<u32>(address);
if ((mtxVal & constant::MtxOwnerMask) != state.thread->handle)
return false;
auto &mtxWaiters = mutexes[address];
if (mtxWaiters.empty()) {
mtxVal = 0;
WriteMemory<u32>(mtxVal, address);
} else
(*mtxWaiters.begin())->flag = true;
return true; return true;
} }
bool KProcess::ConditionalVariableWait(u64 address, u64 timeout) { bool KProcess::MutexUnlock(u64 address) {
std::unique_lock lock(mutexLock);
auto mtx = GetPointer<u32>(address);
auto &mtxWaiters = mutexes[address];
u32 mtxDesired{};
if (!mtxWaiters.empty())
mtxDesired = (*mtxWaiters.begin())->handle | ((mtxWaiters.size() > 1) ? ~constant::MtxOwnerMask : 0);
u32 mtxExpected = (constant::MtxOwnerMask & state.thread->handle) | ~constant::MtxOwnerMask;
if (!__atomic_compare_exchange_n(mtx, &mtxExpected, mtxDesired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
mtxExpected = constant::MtxOwnerMask & state.thread->handle;
if (!__atomic_compare_exchange_n(mtx, &mtxExpected, mtxDesired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
return false;
}
if (mtxDesired) {
auto status = (*mtxWaiters.begin());
status->flag = true;
lock.unlock();
while(status->flag);
lock.lock();
}
return true;
}
bool KProcess::ConditionalVariableWait(u64 conditionalAddress, u64 mutexAddress, u64 timeout) {
std::unique_lock lock(conditionalLock); std::unique_lock lock(conditionalLock);
auto &condWaiters = conditionals[address]; auto &condWaiters = conditionals[conditionalAddress];
std::shared_ptr<WaitStatus> status; std::shared_ptr<WaitStatus> status;
for (auto it = condWaiters.begin();; ++it) { for (auto it = condWaiters.begin();; ++it) {
if (it != condWaiters.end() && (*it)->priority >= state.thread->priority) if (it != condWaiters.end() && (*it)->priority >= state.thread->priority)
continue; continue;
status = std::make_shared<WaitStatus>(state.thread->priority, state.thread->pid); status = std::make_shared<WaitStatus>(state.thread->priority, state.thread->handle, mutexAddress);
condWaiters.insert(it, status); condWaiters.insert(it, status);
break; break;
} }
@ -235,8 +236,12 @@ namespace skyline::kernel::type {
timedOut = true; timedOut = true;
} }
lock.lock(); lock.lock();
if (!status->flag)
timedOut = false;
else
status->flag = false;
for (auto it = condWaiters.begin(); it != condWaiters.end(); ++it) for (auto it = condWaiters.begin(); it != condWaiters.end(); ++it)
if ((*it)->pid == state.thread->pid) { if ((*it)->handle == state.thread->handle) {
condWaiters.erase(it); condWaiters.erase(it);
break; break;
} }
@ -245,10 +250,56 @@ namespace skyline::kernel::type {
} }
void KProcess::ConditionalVariableSignal(u64 address, u64 amount) { void KProcess::ConditionalVariableSignal(u64 address, u64 amount) {
std::lock_guard lock(conditionalLock); std::unique_lock condLock(conditionalLock);
auto &condWaiters = conditionals[address]; auto &condWaiters = conditionals[address];
amount = std::min(condWaiters.size(), amount); u64 count{};
for (size_t i = 0; i < amount; ++i) auto iter = condWaiters.begin();
condWaiters[i]->flag = true; while (iter != condWaiters.end() && count < amount) {
auto &thread = *iter;
auto mtx = GetPointer<u32>(thread->mutexAddress);
u32 mtxValue = __atomic_load_n(mtx, __ATOMIC_SEQ_CST);
while (true) {
u32 mtxDesired{};
if (!mtxValue)
mtxDesired = (constant::MtxOwnerMask & thread->handle);
else if ((mtxValue & constant::MtxOwnerMask) == state.thread->handle)
mtxDesired = mtxValue | (constant::MtxOwnerMask & thread->handle);
else if (mtxValue & ~constant::MtxOwnerMask)
mtxDesired = mtxValue | ~constant::MtxOwnerMask;
else
break;
if (__atomic_compare_exchange_n(mtx, &mtxValue, mtxDesired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
break;
}
if (mtxValue && ((mtxValue & constant::MtxOwnerMask) != state.thread->handle)) {
std::unique_lock mtxLock(mutexLock);
auto &mtxWaiters = mutexes[thread->mutexAddress];
std::shared_ptr<WaitStatus> status;
for (auto it = mtxWaiters.begin();; ++it) {
if (it != mtxWaiters.end() && (*it)->priority >= thread->priority)
continue;
status = std::make_shared<WaitStatus>(thread->priority, thread->handle);
mtxWaiters.insert(it, status);
break;
}
mtxLock.unlock();
while (!status->flag);
mtxLock.lock();
status->flag = false;
for (auto it = mtxWaiters.begin(); it != mtxWaiters.end(); ++it) {
if ((*it)->handle == thread->handle) {
mtxWaiters.erase(it);
break;
}
}
mtxLock.unlock();
}
thread->flag = true;
iter++;
count++;
condLock.unlock();
while(thread->flag);
condLock.lock();
}
} }
} }

View File

@ -7,7 +7,7 @@
#include "KSession.h" #include "KSession.h"
#include "KEvent.h" #include "KEvent.h"
#include <kernel/memory.h> #include <kernel/memory.h>
#include <condition_variable> #include <list>
namespace skyline::kernel::type { namespace skyline::kernel::type {
/** /**
@ -91,9 +91,12 @@ namespace skyline::kernel::type {
struct WaitStatus { struct WaitStatus {
std::atomic_bool flag{false}; //!< The underlying atomic flag of the thread std::atomic_bool flag{false}; //!< The underlying atomic flag of the thread
u8 priority; //!< The priority of the thread u8 priority; //!< The priority of the thread
pid_t pid; //!< The PID of the thread handle_t handle; //!< The handle of the thread
u64 mutexAddress{}; //!< The address of the mutex
WaitStatus(u8 priority, pid_t pid) : priority(priority), pid(pid) {} WaitStatus(u8 priority, handle_t handle) : priority(priority), handle(handle) {}
WaitStatus(u8 priority, handle_t handle, u64 mutexAddress) : priority(priority), handle(handle), mutexAddress(mutexAddress) {}
}; };
handle_t handleIndex = constant::BaseHandleIndex; //!< This is used to keep track of what to map as an handle handle_t handleIndex = constant::BaseHandleIndex; //!< This is used to keep track of what to map as an handle
@ -102,11 +105,11 @@ namespace skyline::kernel::type {
std::unordered_map<handle_t, std::shared_ptr<KObject>> handles; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object std::unordered_map<handle_t, std::shared_ptr<KObject>> handles; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object
std::unordered_map<pid_t, std::shared_ptr<KThread>> threads; //!< A mapping from a PID to it's corresponding KThread object std::unordered_map<pid_t, std::shared_ptr<KThread>> threads; //!< A mapping from a PID to it's corresponding KThread object
std::unordered_map<u64, std::vector<std::shared_ptr<WaitStatus>>> mutexes; //!< A map from a mutex's address to a vector of Mutex objects for threads waiting on it std::unordered_map<u64, std::vector<std::shared_ptr<WaitStatus>>> mutexes; //!< A map from a mutex's address to a vector of Mutex objects for threads waiting on it
std::unordered_map<u64, std::vector<std::shared_ptr<WaitStatus>>> conditionals; //!< A map from a conditional variable's address to a vector of threads waiting on it std::unordered_map<u64, std::list<std::shared_ptr<WaitStatus>>> conditionals; //!< A map from a conditional variable's address to a vector of threads waiting on it
std::vector<std::shared_ptr<TlsPage>> tlsPages; //!< A vector of all allocated TLS pages std::vector<std::shared_ptr<TlsPage>> tlsPages; //!< A vector of all allocated TLS pages
std::shared_ptr<KPrivateMemory> heap; //!< The kernel memory object backing the allocated heap std::shared_ptr<KPrivateMemory> heap; //!< The kernel memory object backing the allocated heap
Mutex mutexLock; //!< This Mutex is to prevent concurrent mutex operations to happen at once Mutex mutexLock; //!< This mutex is to prevent concurrent mutex operations to happen at once
Mutex conditionalLock; //!< This Mutex is to prevent concurrent conditional variable operations to happen at once Mutex conditionalLock; //!< This mutex is to prevent concurrent conditional variable operations to happen at once
/** /**
* @brief Creates a KThread object for the main thread and opens the process's memory file * @brief Creates a KThread object for the main thread and opens the process's memory file
@ -296,9 +299,9 @@ namespace skyline::kernel::type {
* @brief This locks the Mutex at the specified address * @brief This locks the Mutex at the specified address
* @param address The address of the mutex * @param address The address of the mutex
* @param owner The handle of the current mutex owner * @param owner The handle of the current mutex owner
* @param alwaysLock If to return rather than lock if owner tag is not matched * @return If the mutex was successfully locked
*/ */
void MutexLock(u64 address, handle_t owner, bool alwaysLock = false); bool MutexLock(u64 address, handle_t owner);
/** /**
* @brief This unlocks the Mutex at the specified address * @brief This unlocks the Mutex at the specified address
@ -308,11 +311,12 @@ namespace skyline::kernel::type {
bool MutexUnlock(u64 address); bool MutexUnlock(u64 address);
/** /**
* @param address The address of the conditional variable * @param conditionalAddress The address of the conditional variable
* @param mutexAddress The address of the mutex
* @param timeout The amount of time to wait for the conditional variable * @param timeout The amount of time to wait for the conditional variable
* @return If the conditional variable was successfully waited for or timed out * @return If the conditional variable was successfully waited for or timed out
*/ */
bool ConditionalVariableWait(u64 address, u64 timeout); bool ConditionalVariableWait(u64 conditionalAddress, u64 mutexAddress, u64 timeout);
/** /**
* @brief This signals a number of conditional variable waiters * @brief This signals a number of conditional variable waiters