diff --git a/app/src/main/cpp/skyline/common.h b/app/src/main/cpp/skyline/common.h index edc88caa..22c6004f 100644 --- a/app/src/main/cpp/skyline/common.h +++ b/app/src/main/cpp/skyline/common.h @@ -43,7 +43,7 @@ namespace skyline { 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 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 PriorityAn = {19, -8}; //!< The range of priority for Android, taken from https://medium.com/mindorks/exploring-android-thread-priority-5d0542eebbd1 constexpr std::pair 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 diff --git a/app/src/main/cpp/skyline/kernel/svc.cpp b/app/src/main/cpp/skyline/kernel/svc.cpp index 63a31681..683ce3d4 100644 --- a/app/src/main/cpp/skyline/kernel/svc.cpp +++ b/app/src/main/cpp/skyline/kernel/svc.cpp @@ -10,7 +10,7 @@ namespace skyline::kernel::svc { state.logger->Warn("svcSetHeapSize: 'size' not divisible by 2MB: {}", size); return; } - auto& heap = state.process->heap; + auto &heap = state.process->heap; heap->Resize(size); state.ctx->registers.w0 = constant::status::Success; 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); return; } - if(!chunk->state.AttributeChangeAllowed) { + if (!chunk->state.AttributeChangeAllowed) { state.ctx->registers.w0 = constant::status::InvState; state.logger->Warn("svcSetMemoryAttribute: Attribute change not allowed for chunk: 0x{:X}", address); return; @@ -60,29 +60,29 @@ namespace skyline::kernel::svc { const u64 destination = state.ctx->registers.x0; const u64 source = state.ctx->registers.x1; 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.logger->Warn("svcMapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size); return; } - if(!utils::PageAligned(size)) { + if (!utils::PageAligned(size)) { state.ctx->registers.w0 = constant::status::InvSize; state.logger->Warn("svcMapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size); return; } 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.logger->Warn("svcMapMemory: Destination not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size); return; } auto descriptor = state.os->memory.Get(source); - if(!descriptor) { + if (!descriptor) { 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); return; } - if(!descriptor->chunk.state.MapAllowed) { + if (!descriptor->chunk.state.MapAllowed) { 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); return; @@ -90,7 +90,7 @@ namespace skyline::kernel::svc { state.process->NewHandle(destination, size, descriptor->block.permission, memory::MemoryStates::Stack); state.process->CopyMemory(source, destination, size); 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); 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); @@ -101,41 +101,41 @@ namespace skyline::kernel::svc { const u64 source = state.ctx->registers.x0; const u64 destination = state.ctx->registers.x1; 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.logger->Warn("svcUnmapMemory: Addresses not page aligned: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size); return; } - if(!utils::PageAligned(size)) { + if (!utils::PageAligned(size)) { state.ctx->registers.w0 = constant::status::InvSize; state.logger->Warn("svcUnmapMemory: 'size' {}: 0x{:X}", size ? "not page aligned" : "is zero", size); return; } 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.logger->Warn("svcUnmapMemory: Source not within stack region: Source: 0x{:X}, Destination: 0x{:X} (Size: 0x{:X} bytes)", source, destination, size); return; } auto sourceDesc = state.os->memory.Get(source); auto destDesc = state.os->memory.Get(destination); - if(!sourceDesc || !destDesc) { + if (!sourceDesc || !destDesc) { 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); return; } - if(!destDesc->chunk.state.MapAllowed) { + if (!destDesc->chunk.state.MapAllowed) { 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); return; } 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); destObject->item->UpdatePermission(destination, size, sourceDesc->block.permission); state.process->CopyMemory(destination, source, size); 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); 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); @@ -379,7 +379,7 @@ namespace skyline::kernel::svc { state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout); auto start = utils::GetCurrTimeNs(); while (true) { - if(state.thread->cancelSync) { + if (state.thread->cancelSync) { state.thread->cancelSync = false; state.ctx->registers.w0 = constant::status::Interrupted; break; @@ -414,62 +414,63 @@ namespace skyline::kernel::svc { void ArbitrateLock(DeviceState &state) { auto address = state.ctx->registers.x1; if (!utils::WordAligned(address)) { - state.ctx->registers.w0 = constant::status::InvAddress; state.logger->Warn("svcArbitrateLock: 'address' not word aligned: 0x{:X}", address); + state.ctx->registers.w0 = constant::status::InvAddress; return; } auto ownerHandle = state.ctx->registers.w0; auto requesterHandle = state.ctx->registers.w2; if (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.process->MutexLock(address, ownerHandle); - state.logger->Debug("svcArbitrateLock: Locked mutex at 0x{:X} for thread {}", address, state.thread->pid); + state.logger->Debug("svcArbitrateLock: Locking mutex at 0x{:X}", address); + if (state.process->MutexLock(address, ownerHandle)) + 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; } void ArbitrateUnlock(DeviceState &state) { auto address = state.ctx->registers.x0; if (!utils::WordAligned(address)) { - state.ctx->registers.w0 = constant::status::InvAddress; state.logger->Warn("svcArbitrateUnlock: 'address' not word aligned: 0x{:X}", address); + state.ctx->registers.w0 = constant::status::InvAddress; return; } state.logger->Debug("svcArbitrateUnlock: Unlocking mutex at 0x{:X}", address); - if(state.process->MutexUnlock(address)) { - state.ctx->registers.w0 = constant::status::Success; + if (state.process->MutexUnlock(address)) { state.logger->Debug("svcArbitrateUnlock: Unlocked mutex at 0x{:X}", address); + state.ctx->registers.w0 = constant::status::Success; } 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.ctx->registers.w0 = constant::status::InvAddress; } } void WaitProcessWideKeyAtomic(DeviceState &state) { auto mtxAddress = state.ctx->registers.x0; if (!utils::WordAligned(mtxAddress)) { - state.ctx->registers.w0 = constant::status::InvAddress; state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex address not word aligned: 0x{:X}", mtxAddress); + state.ctx->registers.w0 = constant::status::InvAddress; return; } auto condAddress = state.ctx->registers.x1; auto handle = state.ctx->registers.w2; if (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)) { - state.ctx->registers.w0 = constant::status::InvAddress; + if (!state.process->MutexUnlock(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; } auto timeout = state.ctx->registers.x3; state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x{:X}, Timeout: {} ns", mtxAddress, condAddress, timeout); - if (state.process->ConditionalVariableWait(condAddress, timeout)) { - state.ctx->registers.w0 = constant::status::Success; - state.process->MutexLock(mtxAddress, handle, true); + if (state.process->ConditionalVariableWait(condAddress, mtxAddress, timeout)) { state.logger->Debug("svcWaitProcessWideKeyAtomic: Waited for conditional variable and relocked mutex"); + state.ctx->registers.w0 = constant::status::Success; } else { - state.ctx->registers.w0 = constant::status::Timeout; state.logger->Debug("svcWaitProcessWideKeyAtomic: Wait has timed out"); + state.ctx->registers.w0 = constant::status::Timeout; } } diff --git a/app/src/main/cpp/skyline/kernel/types/KProcess.cpp b/app/src/main/cpp/skyline/kernel/types/KProcess.cpp index 61fbfeaa..dbefc216 100644 --- a/app/src/main/cpp/skyline/kernel/types/KProcess.cpp +++ b/app/src/main/cpp/skyline/kernel/types/KProcess.cpp @@ -109,7 +109,7 @@ namespace skyline::kernel::type { } void KProcess::WriteMemory(void *source, const u64 offset, const size_t size, const bool forceGuest) const { - if(!forceGuest) { + if (!forceGuest) { auto destination = GetHostAddress(offset); if (destination) { memcpy(reinterpret_cast(destination), source, size); @@ -131,7 +131,7 @@ namespace skyline::kernel::type { void KProcess::CopyMemory(u64 source, u64 destination, size_t size) const { auto sourceHost = GetHostAddress(source); auto destinationHost = GetHostAddress(destination); - if(sourceHost && destinationHost) { + if (sourceHost && destinationHost) { memcpy(reinterpret_cast(destinationHost), reinterpret_cast(sourceHost), size); } else { if (size <= PAGE_SIZE) { @@ -165,65 +165,66 @@ namespace skyline::kernel::type { 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); - u32 mtxVal = ReadMemory(address); - if (alwaysLock) { - if (!mtxVal) { - state.logger->Warn("Mutex Value was 0"); - mtxVal = (constant::MtxOwnerMask & state.thread->handle); - WriteMemory(mtxVal, address); - return; - // TODO: Replace with atomic CAS - } - } else { - if (mtxVal != (owner | ~constant::MtxOwnerMask)) - return; - } + auto mtx = GetPointer(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 status; for (auto it = mtxWaiters.begin();; ++it) { if (it != mtxWaiters.end() && (*it)->priority >= state.thread->priority) continue; - status = std::make_shared(state.thread->priority, state.thread->pid); + status = std::make_shared(state.thread->priority, state.thread->handle); mtxWaiters.insert(it, status); break; } lock.unlock(); while (!status->flag); lock.lock(); + status->flag = false; for (auto it = mtxWaiters.begin(); it != mtxWaiters.end(); ++it) - if ((*it)->pid == state.thread->pid) { + if ((*it)->handle == state.thread->handle) { mtxWaiters.erase(it); break; } - mtxVal = (constant::MtxOwnerMask & state.thread->handle) | (mtxWaiters.empty() ? 0 : ~constant::MtxOwnerMask); - WriteMemory(mtxVal, address); - lock.unlock(); - } - - bool KProcess::MutexUnlock(u64 address) { - std::lock_guard lock(mutexLock); - u32 mtxVal = ReadMemory(address); - if ((mtxVal & constant::MtxOwnerMask) != state.thread->handle) - return false; - auto &mtxWaiters = mutexes[address]; - if (mtxWaiters.empty()) { - mtxVal = 0; - WriteMemory(mtxVal, address); - } else - (*mtxWaiters.begin())->flag = true; return true; } - bool KProcess::ConditionalVariableWait(u64 address, u64 timeout) { + bool KProcess::MutexUnlock(u64 address) { + std::unique_lock lock(mutexLock); + auto mtx = GetPointer(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); - auto &condWaiters = conditionals[address]; + auto &condWaiters = conditionals[conditionalAddress]; std::shared_ptr status; for (auto it = condWaiters.begin();; ++it) { if (it != condWaiters.end() && (*it)->priority >= state.thread->priority) continue; - status = std::make_shared(state.thread->priority, state.thread->pid); + status = std::make_shared(state.thread->priority, state.thread->handle, mutexAddress); condWaiters.insert(it, status); break; } @@ -235,8 +236,12 @@ namespace skyline::kernel::type { timedOut = true; } lock.lock(); + if (!status->flag) + timedOut = false; + else + status->flag = false; for (auto it = condWaiters.begin(); it != condWaiters.end(); ++it) - if ((*it)->pid == state.thread->pid) { + if ((*it)->handle == state.thread->handle) { condWaiters.erase(it); break; } @@ -245,10 +250,56 @@ namespace skyline::kernel::type { } void KProcess::ConditionalVariableSignal(u64 address, u64 amount) { - std::lock_guard lock(conditionalLock); + std::unique_lock condLock(conditionalLock); auto &condWaiters = conditionals[address]; - amount = std::min(condWaiters.size(), amount); - for (size_t i = 0; i < amount; ++i) - condWaiters[i]->flag = true; + u64 count{}; + auto iter = condWaiters.begin(); + while (iter != condWaiters.end() && count < amount) { + auto &thread = *iter; + auto mtx = GetPointer(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 status; + for (auto it = mtxWaiters.begin();; ++it) { + if (it != mtxWaiters.end() && (*it)->priority >= thread->priority) + continue; + status = std::make_shared(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(); + } } } diff --git a/app/src/main/cpp/skyline/kernel/types/KProcess.h b/app/src/main/cpp/skyline/kernel/types/KProcess.h index 77b75e9e..8a8c654c 100644 --- a/app/src/main/cpp/skyline/kernel/types/KProcess.h +++ b/app/src/main/cpp/skyline/kernel/types/KProcess.h @@ -7,7 +7,7 @@ #include "KSession.h" #include "KEvent.h" #include -#include +#include namespace skyline::kernel::type { /** @@ -91,9 +91,12 @@ namespace skyline::kernel::type { struct WaitStatus { std::atomic_bool flag{false}; //!< The underlying atomic flag 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 @@ -102,11 +105,11 @@ namespace skyline::kernel::type { std::unordered_map> handles; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object std::unordered_map> threads; //!< A mapping from a PID to it's corresponding KThread object std::unordered_map>> mutexes; //!< A map from a mutex's address to a vector of Mutex objects for threads waiting on it - std::unordered_map>> conditionals; //!< A map from a conditional variable's address to a vector of threads waiting on it + std::unordered_map>> conditionals; //!< A map from a conditional variable's address to a vector of threads waiting on it std::vector> tlsPages; //!< A vector of all allocated TLS pages std::shared_ptr heap; //!< The kernel memory object backing the allocated heap - 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 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 /** * @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 * @param address The address of the mutex * @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 @@ -308,11 +311,12 @@ namespace skyline::kernel::type { 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 * @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