summaryrefslogtreecommitdiffstats
path: root/src/core/hle
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/hle')
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp173
-rw-r--r--src/core/hle/kernel/address_arbiter.h32
-rw-r--r--src/core/hle/kernel/errors.h12
-rw-r--r--src/core/hle/kernel/mutex.cpp4
-rw-r--r--src/core/hle/kernel/svc.cpp59
-rw-r--r--src/core/hle/kernel/svc_wrap.h14
-rw-r--r--src/core/hle/kernel/thread.cpp6
-rw-r--r--src/core/hle/kernel/thread.h4
8 files changed, 295 insertions, 9 deletions
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
new file mode 100644
index 000000000..e9c8369d7
--- /dev/null
+++ b/src/core/hle/kernel/address_arbiter.cpp
@@ -0,0 +1,173 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/core.h"
+#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/thread.h"
+#include "core/hle/lock.h"
+#include "core/memory.h"
+
+namespace Kernel {
+namespace AddressArbiter {
+
+// Performs actual address waiting logic.
+static ResultCode WaitForAddress(VAddr address, s64 timeout) {
+ SharedPtr<Thread> current_thread = GetCurrentThread();
+ current_thread->arb_wait_address = address;
+ current_thread->status = THREADSTATUS_WAIT_ARB;
+ current_thread->wakeup_callback = nullptr;
+
+ current_thread->WakeAfterDelay(timeout);
+
+ Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule();
+ return RESULT_TIMEOUT;
+}
+
+// Gets the threads waiting on an address.
+static void GetThreadsWaitingOnAddress(std::vector<SharedPtr<Thread>>& waiting_threads,
+ VAddr address) {
+ auto RetrieveWaitingThreads =
+ [](size_t core_index, std::vector<SharedPtr<Thread>>& waiting_threads, VAddr arb_addr) {
+ const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
+ auto& thread_list = scheduler->GetThreadList();
+
+ for (auto& thread : thread_list) {
+ if (thread->arb_wait_address == arb_addr)
+ waiting_threads.push_back(thread);
+ }
+ };
+
+ // Retrieve a list of all threads that are waiting for this address.
+ RetrieveWaitingThreads(0, waiting_threads, address);
+ RetrieveWaitingThreads(1, waiting_threads, address);
+ RetrieveWaitingThreads(2, waiting_threads, address);
+ RetrieveWaitingThreads(3, waiting_threads, address);
+ // Sort them by priority, such that the highest priority ones come first.
+ std::sort(waiting_threads.begin(), waiting_threads.end(),
+ [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
+ return lhs->current_priority < rhs->current_priority;
+ });
+}
+
+// Wake up num_to_wake (or all) threads in a vector.
+static void WakeThreads(std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_to_wake) {
+ // Only process up to 'target' threads, unless 'target' is <= 0, in which case process
+ // them all.
+ size_t last = waiting_threads.size();
+ if (num_to_wake > 0)
+ last = num_to_wake;
+
+ // Signal the waiting threads.
+ for (size_t i = 0; i < last; i++) {
+ ASSERT(waiting_threads[i]->status = THREADSTATUS_WAIT_ARB);
+ waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
+ waiting_threads[i]->arb_wait_address = 0;
+ waiting_threads[i]->ResumeFromWait();
+ }
+}
+
+// Signals an address being waited on.
+ResultCode SignalToAddress(VAddr address, s32 num_to_wake) {
+ // Get threads waiting on the address.
+ std::vector<SharedPtr<Thread>> waiting_threads;
+ GetThreadsWaitingOnAddress(waiting_threads, address);
+
+ WakeThreads(waiting_threads, num_to_wake);
+ return RESULT_SUCCESS;
+}
+
+// Signals an address being waited on and increments its value if equal to the value argument.
+ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake) {
+ // Ensure that we can write to the address.
+ if (!Memory::IsValidVirtualAddress(address)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if (static_cast<s32>(Memory::Read32(address)) == value) {
+ Memory::Write32(address, static_cast<u32>(value + 1));
+ } else {
+ return ERR_INVALID_STATE;
+ }
+
+ return SignalToAddress(address, num_to_wake);
+}
+
+// Signals an address being waited on and modifies its value based on waiting thread count if equal
+// to the value argument.
+ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
+ s32 num_to_wake) {
+ // Ensure that we can write to the address.
+ if (!Memory::IsValidVirtualAddress(address)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ // Get threads waiting on the address.
+ std::vector<SharedPtr<Thread>> waiting_threads;
+ GetThreadsWaitingOnAddress(waiting_threads, address);
+
+ // Determine the modified value depending on the waiting count.
+ s32 updated_value;
+ if (waiting_threads.size() == 0) {
+ updated_value = value - 1;
+ } else if (num_to_wake <= 0 || waiting_threads.size() <= num_to_wake) {
+ updated_value = value + 1;
+ } else {
+ updated_value = value;
+ }
+
+ if (static_cast<s32>(Memory::Read32(address)) == value) {
+ Memory::Write32(address, static_cast<u32>(updated_value));
+ } else {
+ return ERR_INVALID_STATE;
+ }
+
+ WakeThreads(waiting_threads, num_to_wake);
+ return RESULT_SUCCESS;
+}
+
+// Waits on an address if the value passed is less than the argument value, optionally decrementing.
+ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, bool should_decrement) {
+ // Ensure that we can read the address.
+ if (!Memory::IsValidVirtualAddress(address)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ s32 cur_value = static_cast<s32>(Memory::Read32(address));
+ if (cur_value < value) {
+ Memory::Write32(address, static_cast<u32>(cur_value - 1));
+ } else {
+ return ERR_INVALID_STATE;
+ }
+ // Short-circuit without rescheduling, if timeout is zero.
+ if (timeout == 0) {
+ return RESULT_TIMEOUT;
+ }
+
+ return WaitForAddress(address, timeout);
+}
+
+// Waits on an address if the value passed is equal to the argument value.
+ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) {
+ // Ensure that we can read the address.
+ if (!Memory::IsValidVirtualAddress(address)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+ // Only wait for the address if equal.
+ if (static_cast<s32>(Memory::Read32(address)) != value) {
+ return ERR_INVALID_STATE;
+ }
+ // Short-circuit without rescheduling, if timeout is zero.
+ if (timeout == 0) {
+ return RESULT_TIMEOUT;
+ }
+
+ return WaitForAddress(address, timeout);
+}
+} // namespace AddressArbiter
+} // namespace Kernel
diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h
new file mode 100644
index 000000000..f20f3dbc0
--- /dev/null
+++ b/src/core/hle/kernel/address_arbiter.h
@@ -0,0 +1,32 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Kernel {
+
+namespace AddressArbiter {
+enum class ArbitrationType {
+ WaitIfLessThan = 0,
+ DecrementAndWaitIfLessThan = 1,
+ WaitIfEqual = 2,
+};
+
+enum class SignalType {
+ Signal = 0,
+ IncrementAndSignalIfEqual = 1,
+ ModifyByWaitingCountAndSignalIfEqual = 2,
+};
+
+ResultCode SignalToAddress(VAddr address, s32 num_to_wake);
+ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake);
+ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake);
+
+ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, bool should_decrement);
+ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout);
+} // namespace AddressArbiter
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h
index e1b5430bf..221cb1bb5 100644
--- a/src/core/hle/kernel/errors.h
+++ b/src/core/hle/kernel/errors.h
@@ -20,13 +20,16 @@ enum {
MaxConnectionsReached = 52,
// Confirmed Switch OS error codes
- MisalignedAddress = 102,
+ InvalidAddress = 102,
+ InvalidMemoryState = 106,
InvalidProcessorId = 113,
InvalidHandle = 114,
InvalidCombination = 116,
Timeout = 117,
SynchronizationCanceled = 118,
TooLarge = 119,
+ InvalidEnumValue = 120,
+ InvalidState = 125,
};
}
@@ -39,14 +42,15 @@ constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE(-1);
constexpr ResultCode ERR_PORT_NAME_TOO_LONG(-1);
constexpr ResultCode ERR_WRONG_PERMISSION(-1);
constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED(-1);
-constexpr ResultCode ERR_INVALID_ENUM_VALUE(-1);
+constexpr ResultCode ERR_INVALID_ENUM_VALUE(ErrorModule::Kernel, ErrCodes::InvalidEnumValue);
constexpr ResultCode ERR_INVALID_ENUM_VALUE_FND(-1);
constexpr ResultCode ERR_INVALID_COMBINATION(-1);
constexpr ResultCode ERR_INVALID_COMBINATION_KERNEL(-1);
constexpr ResultCode ERR_OUT_OF_MEMORY(-1);
-constexpr ResultCode ERR_INVALID_ADDRESS(-1);
-constexpr ResultCode ERR_INVALID_ADDRESS_STATE(-1);
+constexpr ResultCode ERR_INVALID_ADDRESS(ErrorModule::Kernel, ErrCodes::InvalidAddress);
+constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::InvalidMemoryState);
constexpr ResultCode ERR_INVALID_HANDLE(ErrorModule::Kernel, ErrCodes::InvalidHandle);
+constexpr ResultCode ERR_INVALID_STATE(ErrorModule::Kernel, ErrCodes::InvalidState);
constexpr ResultCode ERR_INVALID_POINTER(-1);
constexpr ResultCode ERR_INVALID_OBJECT_ADDR(-1);
constexpr ResultCode ERR_NOT_AUTHORIZED(-1);
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp
index bc144f3de..65560226d 100644
--- a/src/core/hle/kernel/mutex.cpp
+++ b/src/core/hle/kernel/mutex.cpp
@@ -59,7 +59,7 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
Handle requesting_thread_handle) {
// The mutex address must be 4-byte aligned
if ((address % sizeof(u32)) != 0) {
- return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress);
+ return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidAddress);
}
SharedPtr<Thread> holding_thread = g_handle_table.Get<Thread>(holding_thread_handle);
@@ -97,7 +97,7 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
ResultCode Mutex::Release(VAddr address) {
// The mutex address must be 4-byte aligned
if ((address % sizeof(u32)) != 0) {
- return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress);
+ return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidAddress);
}
auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address);
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 69ba7b777..1a36e0d02 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -11,6 +11,7 @@
#include "common/string_util.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/event.h"
@@ -580,7 +581,7 @@ static void SleepThread(s64 nanoseconds) {
Core::System::GetInstance().PrepareReschedule();
}
-/// Signal process wide key atomic
+/// Wait process wide key atomic
static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_variable_addr,
Handle thread_handle, s64 nano_seconds) {
NGLOG_TRACE(
@@ -689,6 +690,58 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
return RESULT_SUCCESS;
}
+// Wait for an address (via Address Arbiter)
+static ResultCode WaitForAddress(VAddr address, u32 type, s32 value, s64 timeout) {
+ NGLOG_WARNING(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, timeout={}",
+ address, type, value, timeout);
+ // If the passed address is a kernel virtual address, return invalid memory state.
+ if (Memory::IsKernelVirtualAddress(address)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+ // If the address is not properly aligned to 4 bytes, return invalid address.
+ if (address % sizeof(u32) != 0) {
+ return ERR_INVALID_ADDRESS;
+ }
+
+ switch (static_cast<AddressArbiter::ArbitrationType>(type)) {
+ case AddressArbiter::ArbitrationType::WaitIfLessThan:
+ return AddressArbiter::WaitForAddressIfLessThan(address, value, timeout, false);
+ case AddressArbiter::ArbitrationType::DecrementAndWaitIfLessThan:
+ return AddressArbiter::WaitForAddressIfLessThan(address, value, timeout, true);
+ case AddressArbiter::ArbitrationType::WaitIfEqual:
+ return AddressArbiter::WaitForAddressIfEqual(address, value, timeout);
+ default:
+ return ERR_INVALID_ENUM_VALUE;
+ }
+}
+
+// Signals to an address (via Address Arbiter)
+static ResultCode SignalToAddress(VAddr address, u32 type, s32 value, s32 num_to_wake) {
+ NGLOG_WARNING(Kernel_SVC,
+ "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, num_to_wake=0x{:X}", address,
+ type, value, num_to_wake);
+ // If the passed address is a kernel virtual address, return invalid memory state.
+ if (Memory::IsKernelVirtualAddress(address)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+ // If the address is not properly aligned to 4 bytes, return invalid address.
+ if (address % sizeof(u32) != 0) {
+ return ERR_INVALID_ADDRESS;
+ }
+
+ switch (static_cast<AddressArbiter::SignalType>(type)) {
+ case AddressArbiter::SignalType::Signal:
+ return AddressArbiter::SignalToAddress(address, num_to_wake);
+ case AddressArbiter::SignalType::IncrementAndSignalIfEqual:
+ return AddressArbiter::IncrementAndSignalToAddressIfEqual(address, value, num_to_wake);
+ case AddressArbiter::SignalType::ModifyByWaitingCountAndSignalIfEqual:
+ return AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(address, value,
+ num_to_wake);
+ default:
+ return ERR_INVALID_ENUM_VALUE;
+ }
+}
+
/// This returns the total CPU ticks elapsed since the CPU was powered-on
static u64 GetSystemTick() {
const u64 result{CoreTiming::GetTicks()};
@@ -861,8 +914,8 @@ static const FunctionDef SVC_Table[] = {
{0x31, nullptr, "GetResourceLimitCurrentValue"},
{0x32, SvcWrap<SetThreadActivity>, "SetThreadActivity"},
{0x33, SvcWrap<GetThreadContext>, "GetThreadContext"},
- {0x34, nullptr, "WaitForAddress"},
- {0x35, nullptr, "SignalToAddress"},
+ {0x34, SvcWrap<WaitForAddress>, "WaitForAddress"},
+ {0x35, SvcWrap<SignalToAddress>, "SignalToAddress"},
{0x36, nullptr, "Unknown"},
{0x37, nullptr, "Unknown"},
{0x38, nullptr, "Unknown"},
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index 40aa88cc1..79c3fe31b 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.h
@@ -179,6 +179,20 @@ void SvcWrap() {
FuncReturn(retval);
}
+template <ResultCode func(u64, u32, s32, s64)>
+void SvcWrap() {
+ FuncReturn(
+ func(PARAM(0), (u32)(PARAM(1) & 0xFFFFFFFF), (s32)(PARAM(2) & 0xFFFFFFFF), (s64)PARAM(3))
+ .raw);
+}
+
+template <ResultCode func(u64, u32, s32, s32)>
+void SvcWrap() {
+ FuncReturn(func(PARAM(0), (u32)(PARAM(1) & 0xFFFFFFFF), (s32)(PARAM(2) & 0xFFFFFFFF),
+ (s32)(PARAM(3) & 0xFFFFFFFF))
+ .raw);
+}
+
////////////////////////////////////////////////////////////////////////////////////////////////////
// Function wrappers that return type u32
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index cffa7ca83..2f333ec34 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -140,6 +140,11 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
}
}
+ if (thread->arb_wait_address != 0) {
+ ASSERT(thread->status == THREADSTATUS_WAIT_ARB);
+ thread->arb_wait_address = 0;
+ }
+
if (resume)
thread->ResumeFromWait();
}
@@ -179,6 +184,7 @@ void Thread::ResumeFromWait() {
case THREADSTATUS_WAIT_SLEEP:
case THREADSTATUS_WAIT_IPC:
case THREADSTATUS_WAIT_MUTEX:
+ case THREADSTATUS_WAIT_ARB:
break;
case THREADSTATUS_READY:
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 1d2da6d50..f1e759802 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -45,6 +45,7 @@ enum ThreadStatus {
THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false
THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true
THREADSTATUS_WAIT_MUTEX, ///< Waiting due to an ArbitrateLock/WaitProcessWideKey svc
+ THREADSTATUS_WAIT_ARB, ///< Waiting due to a SignalToAddress/WaitForAddress svc
THREADSTATUS_DORMANT, ///< Created but not yet made ready
THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated
};
@@ -230,6 +231,9 @@ public:
VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address
Handle wait_handle; ///< The handle used to wait for the mutex.
+ // If waiting for an AddressArbiter, this is the address being waited on.
+ VAddr arb_wait_address{0};
+
std::string name;
/// Handle used by guest emulated application to access this thread