From 1884d98163c2bf61f80b3de7d377a1c36ab2330f Mon Sep 17 00:00:00 2001 From: PixelyIon Date: Sun, 21 Feb 2021 23:38:13 +0530 Subject: [PATCH] Implement Address Arbiter The entirety of the address arbiter is implemented in this commit, all three arbitration types: `WaitIfLessThan`, `DecrementAndWaitIfLessThan` and `WaitIfEqual`, and all three signal types: `Signal`, `SignalAndIncrementIfEqual` and `SignalAndModifyBasedOnWaitingThreadCountIfEqual` have been implemented. This allows any application which uses levent (Light Events) to function which includes titles such as ARMS. --- app/src/main/cpp/skyline/kernel/svc.cpp | 115 ++++++++++++++++++ app/src/main/cpp/skyline/kernel/svc.h | 16 ++- .../cpp/skyline/kernel/types/KProcess.cpp | 48 ++++++++ .../main/cpp/skyline/kernel/types/KProcess.h | 10 ++ 4 files changed, 187 insertions(+), 2 deletions(-) diff --git a/app/src/main/cpp/skyline/kernel/svc.cpp b/app/src/main/cpp/skyline/kernel/svc.cpp index 764fbf5e..7d93901d 100644 --- a/app/src/main/cpp/skyline/kernel/svc.cpp +++ b/app/src/main/cpp/skyline/kernel/svc.cpp @@ -1032,4 +1032,119 @@ namespace skyline::kernel::svc { state.ctx->gpr.w0 = Result{}; } + + void WaitForAddress(const DeviceState &state) { + auto address{reinterpret_cast(state.ctx->gpr.x0)}; + if (!util::WordAligned(address)) [[unlikely]] { + state.logger->Warn("svcWaitForAddress: 'address' not word aligned: 0x{:X}", address); + state.ctx->gpr.w0 = result::InvalidAddress; + return; + } + + enum class ArbitrationType : u32 { + WaitIfLessThan = 0, + DecrementAndWaitIfLessThan = 1, + WaitIfEqual = 2, + } arbitrationType{static_cast(static_cast(state.ctx->gpr.w1))}; + u32 value{state.ctx->gpr.w2}; + i64 timeout{static_cast(state.ctx->gpr.x3)}; + + Result result; + switch (arbitrationType) { + case ArbitrationType::WaitIfLessThan: + state.logger->Debug("svcWaitForAddress: Waiting on 0x{:X} if less than {} for {}ns", address, value, timeout); + result = state.process->WaitForAddress(address, value, timeout, [](u32 *address, u32 value) { + return *address < value; + }); + break; + + case ArbitrationType::DecrementAndWaitIfLessThan: + state.logger->Debug("svcWaitForAddress: Waiting on and decrementing 0x{:X} if less than {} for {}ns", address, value, timeout); + result = state.process->WaitForAddress(address, value, timeout, [](u32 *address, u32 value) { + u32 userValue{__atomic_load_n(address, __ATOMIC_SEQ_CST)}; + do { + if (value <= userValue) [[unlikely]] // We want to explicitly decrement **after** the check + return false; + } while (!__atomic_compare_exchange_n(address, &userValue, userValue - 1, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)); + return true; + }); + break; + + case ArbitrationType::WaitIfEqual: + state.logger->Debug("svcWaitForAddress: Waiting on 0x{:X} if equal to {} for {}ns", address, value, timeout); + result = state.process->WaitForAddress(address, value, timeout, [](u32 *address, u32 value) { + return *address == value; + }); + break; + + default: + [[unlikely]] + state.logger->Error("svcWaitForAddress: 'arbitrationType' invalid: {}", arbitrationType); + state.ctx->gpr.w0 = result::InvalidEnumValue; + return; + } + + if (result == Result{}) + [[likely]] + state.logger->Debug("svcWaitForAddress: Waited on 0x{:X} successfully", address); + else if (result == result::TimedOut) + state.logger->Debug("svcWaitForAddress: Wait on 0x{:X} has timed out after {}ns", address, timeout); + else if (result == result::InvalidState) + state.logger->Debug("svcWaitForAddress: The value at 0x{:X} did not satisfy the arbitration condition", address); + + state.ctx->gpr.w0 = result; + } + + void SignalToAddress(const DeviceState &state) { + auto address{reinterpret_cast(state.ctx->gpr.x0)}; + if (!util::WordAligned(address)) [[unlikely]] { + state.logger->Warn("svcWaitForAddress: 'address' not word aligned: 0x{:X}", address); + state.ctx->gpr.w0 = result::InvalidAddress; + return; + } + + enum class SignalType : u32 { + Signal = 0, + SignalAndIncrementIfEqual = 1, + SignalAndModifyBasedOnWaitingThreadCountIfEqual = 2, + } signalType{static_cast(static_cast(state.ctx->gpr.w1))}; + u32 value{state.ctx->gpr.w2}; + i32 count{static_cast(state.ctx->gpr.w3)}; + + Result result; + switch (signalType) { + case SignalType::Signal: + state.logger->Debug("svcSignalToAddress: Signalling 0x{:X} for {} waiters", address, count); + result = state.process->SignalToAddress(address, value, count); + break; + + case SignalType::SignalAndIncrementIfEqual: + state.logger->Debug("svcSignalToAddress: Signalling 0x{:X} and incrementing if equal to {} for {} waiters", address, value, count); + result = state.process->SignalToAddress(address, value, count, [](u32 *address, u32 value, u32) { + return __atomic_compare_exchange_n(address, &value, value + 1, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + }); + break; + + case SignalType::SignalAndModifyBasedOnWaitingThreadCountIfEqual: + state.logger->Debug("svcSignalToAddress: Signalling 0x{:X} and setting to waiting thread count if equal to {} for {} waiters", address, value, count); + result = state.process->SignalToAddress(address, value, count, [](u32 *address, u32 value, u32 waiterCount) { + return __atomic_compare_exchange_n(address, &value, waiterCount, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + }); + break; + + default: + [[unlikely]] + state.logger->Error("svcSignalToAddress: 'signalType' invalid: {}", signalType); + state.ctx->gpr.w0 = result::InvalidEnumValue; + return; + } + + if (result == Result{}) + [[likely]] + state.logger->Debug("svcSignalToAddress: Signalled 0x{:X} for {} successfully", address, count); + else if (result == result::InvalidState) + state.logger->Debug("svcSignalToAddress: The value at 0x{:X} did not satisfy the mutation condition", address); + + state.ctx->gpr.w0 = Result{}; + } } diff --git a/app/src/main/cpp/skyline/kernel/svc.h b/app/src/main/cpp/skyline/kernel/svc.h index d342ea70..7e9aa20d 100644 --- a/app/src/main/cpp/skyline/kernel/svc.h +++ b/app/src/main/cpp/skyline/kernel/svc.h @@ -216,6 +216,18 @@ namespace skyline::kernel::svc { */ void UnmapPhysicalMemory(const DeviceState &state); + /** + * @brief Waits on an address based on the value of the address + * @url https://switchbrew.org/wiki/SVC#WaitForAddress + */ + void WaitForAddress(const DeviceState &state); + + /** + * @brief Signals a thread which is waiting on an address + * @url https://switchbrew.org/wiki/SVC#SignalToAddress + */ + void SignalToAddress(const DeviceState &state); + /** * @brief The SVC Table maps all SVCs to their corresponding functions */ @@ -272,8 +284,8 @@ namespace skyline::kernel::svc { nullptr, // 0x31 nullptr, // 0x32 nullptr, // 0x33 - nullptr, // 0x34 - nullptr, // 0x35 + WaitForAddress, // 0x34 + SignalToAddress, // 0x35 nullptr, // 0x36 nullptr, // 0x37 nullptr, // 0x38 diff --git a/app/src/main/cpp/skyline/kernel/types/KProcess.cpp b/app/src/main/cpp/skyline/kernel/types/KProcess.cpp index 251bde73..e9ff5977 100644 --- a/app/src/main/cpp/skyline/kernel/types/KProcess.cpp +++ b/app/src/main/cpp/skyline/kernel/types/KProcess.cpp @@ -285,4 +285,52 @@ namespace skyline::kernel::type { if (it == queue.second) __atomic_store_n(key, false, __ATOMIC_SEQ_CST); // We need to update the boolean flag denoting that there are no more threads waiting on this conditional variable } + + Result KProcess::WaitForAddress(u32 *address, u32 value, i64 timeout, bool (*arbitrationFunction)(u32 *, u32)) { + { + std::lock_guard lock(syncWaiterMutex); + if (!arbitrationFunction(address, value)) [[unlikely]] + return result::InvalidState; + + auto queue{syncWaiters.equal_range(address)}; + syncWaiters.insert(std::upper_bound(queue.first, queue.second, state.thread->priority.load(), [](const i8 priority, const SyncWaiters::value_type &it) { return it.second->priority > priority; }), {address, state.thread}); + + state.scheduler->RemoveThread(); + } + + if (timeout > 0 && !state.scheduler->TimedWaitSchedule(std::chrono::nanoseconds(timeout))) { + std::unique_lock lock(syncWaiterMutex); + auto queue{syncWaiters.equal_range(address)}; + auto iterator{std::find(queue.first, queue.second, SyncWaiters::value_type{address, state.thread})}; + if (iterator != queue.second) + if (syncWaiters.erase(iterator) == queue.second) + __atomic_store_n(address, false, __ATOMIC_SEQ_CST); + + lock.unlock(); + state.scheduler->InsertThread(state.thread); + state.scheduler->WaitSchedule(); + + return result::TimedOut; + } else { + state.scheduler->WaitSchedule(false); + } + + return {}; + } + + Result KProcess::SignalToAddress(u32 *address, u32 value, i32 amount, bool(*mutateFunction)(u32 *address, u32 value, u32 waiterCount)) { + std::lock_guard lock(syncWaiterMutex); + auto queue{syncWaiters.equal_range(address)}; + + if (mutateFunction) + if (!mutateFunction(address, value, (amount <= 0) ? 0 : std::min(static_cast(std::distance(queue.first, queue.second) - amount), 0U))) [[unlikely]] + return result::InvalidState; + + i32 waiterCount{amount}; + if (queue.first != queue.second) + for (auto it{queue.first}; it != queue.second && (amount <= 0 || waiterCount); it = syncWaiters.erase(it), waiterCount--) + state.scheduler->InsertThread(it->second); + + return {}; + } } diff --git a/app/src/main/cpp/skyline/kernel/types/KProcess.h b/app/src/main/cpp/skyline/kernel/types/KProcess.h index 027c6131..0eca5ae9 100644 --- a/app/src/main/cpp/skyline/kernel/types/KProcess.h +++ b/app/src/main/cpp/skyline/kernel/types/KProcess.h @@ -222,6 +222,16 @@ namespace skyline { * @brief Signals the conditional variable at the specified address */ void ConditionalVariableSignal(u32 *key, u64 amount); + + /** + * @brief Waits on the supplied address with the specified arbitration function + */ + Result WaitForAddress(u32 *address, u32 value, i64 timeout, bool(*arbitrationFunction)(u32* address, u32 value)); + + /** + * @brief Signals a variable amount of waiters at the supplied address + */ + Result SignalToAddress(u32 *address, u32 value, i32 amount, bool(*mutateFunction)(u32* address, u32 value, u32 waiterCount) = nullptr); }; } }