3 changed files with 437 additions and 0 deletions
-
2src/core/CMakeLists.txt
-
365src/core/hle/kernel/k_address_arbiter.cpp
-
70src/core/hle/kernel/k_address_arbiter.h
@ -0,0 +1,365 @@ |
|||||
|
// Copyright 2021 yuzu Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include "core/arm/exclusive_monitor.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/hle/kernel/k_address_arbiter.h"
|
||||
|
#include "core/hle/kernel/k_scheduler.h"
|
||||
|
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
||||
|
#include "core/hle/kernel/kernel.h"
|
||||
|
#include "core/hle/kernel/svc_results.h"
|
||||
|
#include "core/hle/kernel/thread.h"
|
||||
|
#include "core/hle/kernel/time_manager.h"
|
||||
|
#include "core/memory.h"
|
||||
|
|
||||
|
namespace Kernel { |
||||
|
|
||||
|
KAddressArbiter::KAddressArbiter(Core::System& system_) |
||||
|
: system{system_}, kernel{system.Kernel()} {} |
||||
|
KAddressArbiter::~KAddressArbiter() = default; |
||||
|
|
||||
|
namespace { |
||||
|
|
||||
|
bool ReadFromUser(Core::System& system, s32* out, VAddr address) { |
||||
|
*out = system.Memory().Read32(address); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
bool DecrementIfLessThan(Core::System& system, s32* out, VAddr address, s32 value) { |
||||
|
auto& monitor = system.Monitor(); |
||||
|
const auto current_core = system.CurrentCoreIndex(); |
||||
|
|
||||
|
// TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
|
||||
|
// TODO(bunnei): We should call CanAccessAtomic(..) here.
|
||||
|
|
||||
|
// Load the value from the address.
|
||||
|
const s32 current_value = static_cast<s32>(monitor.ExclusiveRead32(current_core, address)); |
||||
|
|
||||
|
// Compare it to the desired one.
|
||||
|
if (current_value < value) { |
||||
|
// If less than, we want to try to decrement.
|
||||
|
const s32 decrement_value = current_value - 1; |
||||
|
|
||||
|
// Decrement and try to store.
|
||||
|
if (!monitor.ExclusiveWrite32(current_core, address, static_cast<u32>(decrement_value))) { |
||||
|
// If we failed to store, try again.
|
||||
|
DecrementIfLessThan(system, out, address, value); |
||||
|
} |
||||
|
} else { |
||||
|
// Otherwise, clear our exclusive hold and finish
|
||||
|
monitor.ClearExclusive(); |
||||
|
} |
||||
|
|
||||
|
// We're done.
|
||||
|
*out = current_value; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
bool UpdateIfEqual(Core::System& system, s32* out, VAddr address, s32 value, s32 new_value) { |
||||
|
auto& monitor = system.Monitor(); |
||||
|
const auto current_core = system.CurrentCoreIndex(); |
||||
|
|
||||
|
// TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
|
||||
|
// TODO(bunnei): We should call CanAccessAtomic(..) here.
|
||||
|
|
||||
|
// Load the value from the address.
|
||||
|
const s32 current_value = static_cast<s32>(monitor.ExclusiveRead32(current_core, address)); |
||||
|
|
||||
|
// Compare it to the desired one.
|
||||
|
if (current_value == value) { |
||||
|
// If equal, we want to try to write the new value.
|
||||
|
|
||||
|
// Try to store.
|
||||
|
if (!monitor.ExclusiveWrite32(current_core, address, static_cast<u32>(new_value))) { |
||||
|
// If we failed to store, try again.
|
||||
|
UpdateIfEqual(system, out, address, value, new_value); |
||||
|
} |
||||
|
} else { |
||||
|
// Otherwise, clear our exclusive hold and finish.
|
||||
|
monitor.ClearExclusive(); |
||||
|
} |
||||
|
|
||||
|
// We're done.
|
||||
|
*out = current_value; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
} // namespace
|
||||
|
|
||||
|
ResultCode KAddressArbiter::Signal(VAddr addr, s32 count) { |
||||
|
// Perform signaling.
|
||||
|
s32 num_waiters{}; |
||||
|
{ |
||||
|
KScopedSchedulerLock sl(kernel); |
||||
|
|
||||
|
auto it = thread_tree.nfind_light({addr, -1}); |
||||
|
while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) && |
||||
|
(it->GetAddressArbiterKey() == addr)) { |
||||
|
Thread* target_thread = std::addressof(*it); |
||||
|
target_thread->SetSyncedObject(nullptr, RESULT_SUCCESS); |
||||
|
|
||||
|
ASSERT(target_thread->IsWaitingForAddressArbiter()); |
||||
|
target_thread->Wakeup(); |
||||
|
|
||||
|
it = thread_tree.erase(it); |
||||
|
target_thread->ClearAddressArbiter(); |
||||
|
++num_waiters; |
||||
|
} |
||||
|
} |
||||
|
return RESULT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
ResultCode KAddressArbiter::SignalAndIncrementIfEqual(VAddr addr, s32 value, s32 count) { |
||||
|
// Perform signaling.
|
||||
|
s32 num_waiters{}; |
||||
|
{ |
||||
|
KScopedSchedulerLock sl(kernel); |
||||
|
|
||||
|
// Check the userspace value.
|
||||
|
s32 user_value{}; |
||||
|
R_UNLESS(UpdateIfEqual(system, std::addressof(user_value), addr, value, value + 1), |
||||
|
Svc::ResultInvalidCurrentMemory); |
||||
|
R_UNLESS(user_value == value, Svc::ResultInvalidState); |
||||
|
|
||||
|
auto it = thread_tree.nfind_light({addr, -1}); |
||||
|
while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) && |
||||
|
(it->GetAddressArbiterKey() == addr)) { |
||||
|
Thread* target_thread = std::addressof(*it); |
||||
|
target_thread->SetSyncedObject(nullptr, RESULT_SUCCESS); |
||||
|
|
||||
|
ASSERT(target_thread->IsWaitingForAddressArbiter()); |
||||
|
target_thread->Wakeup(); |
||||
|
|
||||
|
it = thread_tree.erase(it); |
||||
|
target_thread->ClearAddressArbiter(); |
||||
|
++num_waiters; |
||||
|
} |
||||
|
} |
||||
|
return RESULT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
ResultCode KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32 value, s32 count) { |
||||
|
// Perform signaling.
|
||||
|
s32 num_waiters{}; |
||||
|
{ |
||||
|
KScopedSchedulerLock sl(kernel); |
||||
|
|
||||
|
auto it = thread_tree.nfind_light({addr, -1}); |
||||
|
// Determine the updated value.
|
||||
|
s32 new_value{}; |
||||
|
if (/*GetTargetFirmware() >= TargetFirmware_7_0_0*/ true) { |
||||
|
if (count <= 0) { |
||||
|
if ((it != thread_tree.end()) && (it->GetAddressArbiterKey() == addr)) { |
||||
|
new_value = value - 2; |
||||
|
} else { |
||||
|
new_value = value + 1; |
||||
|
} |
||||
|
} else { |
||||
|
if ((it != thread_tree.end()) && (it->GetAddressArbiterKey() == addr)) { |
||||
|
auto tmp_it = it; |
||||
|
s32 tmp_num_waiters{}; |
||||
|
while ((++tmp_it != thread_tree.end()) && |
||||
|
(tmp_it->GetAddressArbiterKey() == addr)) { |
||||
|
if ((tmp_num_waiters++) >= count) { |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (tmp_num_waiters < count) { |
||||
|
new_value = value - 1; |
||||
|
} else { |
||||
|
new_value = value; |
||||
|
} |
||||
|
} else { |
||||
|
new_value = value + 1; |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
if (count <= 0) { |
||||
|
if ((it != thread_tree.end()) && (it->GetAddressArbiterKey() == addr)) { |
||||
|
new_value = value - 1; |
||||
|
} else { |
||||
|
new_value = value + 1; |
||||
|
} |
||||
|
} else { |
||||
|
auto tmp_it = it; |
||||
|
s32 tmp_num_waiters{}; |
||||
|
while ((tmp_it != thread_tree.end()) && (tmp_it->GetAddressArbiterKey() == addr) && |
||||
|
(tmp_num_waiters < count + 1)) { |
||||
|
++tmp_num_waiters; |
||||
|
++tmp_it; |
||||
|
} |
||||
|
|
||||
|
if (tmp_num_waiters == 0) { |
||||
|
new_value = value + 1; |
||||
|
} else if (tmp_num_waiters <= count) { |
||||
|
new_value = value - 1; |
||||
|
} else { |
||||
|
new_value = value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Check the userspace value.
|
||||
|
s32 user_value{}; |
||||
|
bool succeeded{}; |
||||
|
if (value != new_value) { |
||||
|
succeeded = UpdateIfEqual(system, std::addressof(user_value), addr, value, new_value); |
||||
|
} else { |
||||
|
succeeded = ReadFromUser(system, std::addressof(user_value), addr); |
||||
|
} |
||||
|
|
||||
|
R_UNLESS(succeeded, Svc::ResultInvalidCurrentMemory); |
||||
|
R_UNLESS(user_value == value, Svc::ResultInvalidState); |
||||
|
|
||||
|
while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) && |
||||
|
(it->GetAddressArbiterKey() == addr)) { |
||||
|
Thread* target_thread = std::addressof(*it); |
||||
|
target_thread->SetSyncedObject(nullptr, RESULT_SUCCESS); |
||||
|
|
||||
|
ASSERT(target_thread->IsWaitingForAddressArbiter()); |
||||
|
target_thread->Wakeup(); |
||||
|
|
||||
|
it = thread_tree.erase(it); |
||||
|
target_thread->ClearAddressArbiter(); |
||||
|
++num_waiters; |
||||
|
} |
||||
|
} |
||||
|
return RESULT_SUCCESS; |
||||
|
} |
||||
|
|
||||
|
ResultCode KAddressArbiter::WaitIfLessThan(VAddr addr, s32 value, bool decrement, s64 timeout) { |
||||
|
// Prepare to wait.
|
||||
|
Thread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread(); |
||||
|
Handle timer = InvalidHandle; |
||||
|
|
||||
|
{ |
||||
|
KScopedSchedulerLockAndSleep slp(kernel, timer, cur_thread, timeout); |
||||
|
|
||||
|
// Check that the thread isn't terminating.
|
||||
|
if (cur_thread->IsTerminationRequested()) { |
||||
|
slp.CancelSleep(); |
||||
|
return Svc::ResultTerminationRequested; |
||||
|
} |
||||
|
|
||||
|
// Set the synced object.
|
||||
|
cur_thread->SetSyncedObject(nullptr, Svc::ResultTimedOut); |
||||
|
|
||||
|
// Read the value from userspace.
|
||||
|
s32 user_value{}; |
||||
|
bool succeeded{}; |
||||
|
if (decrement) { |
||||
|
succeeded = DecrementIfLessThan(system, std::addressof(user_value), addr, value); |
||||
|
} else { |
||||
|
succeeded = ReadFromUser(system, std::addressof(user_value), addr); |
||||
|
} |
||||
|
|
||||
|
if (!succeeded) { |
||||
|
slp.CancelSleep(); |
||||
|
return Svc::ResultInvalidCurrentMemory; |
||||
|
} |
||||
|
|
||||
|
// Check that the value is less than the specified one.
|
||||
|
if (user_value >= value) { |
||||
|
slp.CancelSleep(); |
||||
|
return Svc::ResultInvalidState; |
||||
|
} |
||||
|
|
||||
|
// Check that the timeout is non-zero.
|
||||
|
if (timeout == 0) { |
||||
|
slp.CancelSleep(); |
||||
|
return Svc::ResultTimedOut; |
||||
|
} |
||||
|
|
||||
|
// Set the arbiter.
|
||||
|
cur_thread->SetAddressArbiter(std::addressof(thread_tree), addr); |
||||
|
thread_tree.insert(*cur_thread); |
||||
|
cur_thread->SetState(ThreadState::Waiting); |
||||
|
} |
||||
|
|
||||
|
// Cancel the timer wait.
|
||||
|
if (timer != InvalidHandle) { |
||||
|
auto& time_manager = kernel.TimeManager(); |
||||
|
time_manager.UnscheduleTimeEvent(timer); |
||||
|
} |
||||
|
|
||||
|
// Remove from the address arbiter.
|
||||
|
{ |
||||
|
KScopedSchedulerLock sl(kernel); |
||||
|
|
||||
|
if (cur_thread->IsWaitingForAddressArbiter()) { |
||||
|
thread_tree.erase(thread_tree.iterator_to(*cur_thread)); |
||||
|
cur_thread->ClearAddressArbiter(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Get the result.
|
||||
|
KSynchronizationObject* dummy{}; |
||||
|
return cur_thread->GetWaitResult(std::addressof(dummy)); |
||||
|
} |
||||
|
|
||||
|
ResultCode KAddressArbiter::WaitIfEqual(VAddr addr, s32 value, s64 timeout) { |
||||
|
// Prepare to wait.
|
||||
|
Thread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread(); |
||||
|
Handle timer = InvalidHandle; |
||||
|
|
||||
|
{ |
||||
|
KScopedSchedulerLockAndSleep slp(kernel, timer, cur_thread, timeout); |
||||
|
|
||||
|
// Check that the thread isn't terminating.
|
||||
|
if (cur_thread->IsTerminationRequested()) { |
||||
|
slp.CancelSleep(); |
||||
|
return Svc::ResultTerminationRequested; |
||||
|
} |
||||
|
|
||||
|
// Set the synced object.
|
||||
|
cur_thread->SetSyncedObject(nullptr, Svc::ResultTimedOut); |
||||
|
|
||||
|
// Read the value from userspace.
|
||||
|
s32 user_value{}; |
||||
|
if (!ReadFromUser(system, std::addressof(user_value), addr)) { |
||||
|
slp.CancelSleep(); |
||||
|
return Svc::ResultInvalidCurrentMemory; |
||||
|
} |
||||
|
|
||||
|
// Check that the value is equal.
|
||||
|
if (value != user_value) { |
||||
|
slp.CancelSleep(); |
||||
|
return Svc::ResultInvalidState; |
||||
|
} |
||||
|
|
||||
|
// Check that the timeout is non-zero.
|
||||
|
if (timeout == 0) { |
||||
|
slp.CancelSleep(); |
||||
|
return Svc::ResultTimedOut; |
||||
|
} |
||||
|
|
||||
|
// Set the arbiter.
|
||||
|
cur_thread->SetAddressArbiter(std::addressof(thread_tree), addr); |
||||
|
thread_tree.insert(*cur_thread); |
||||
|
cur_thread->SetState(ThreadState::Waiting); |
||||
|
} |
||||
|
|
||||
|
// Cancel the timer wait.
|
||||
|
if (timer != InvalidHandle) { |
||||
|
auto& time_manager = kernel.TimeManager(); |
||||
|
time_manager.UnscheduleTimeEvent(timer); |
||||
|
} |
||||
|
|
||||
|
// Remove from the address arbiter.
|
||||
|
{ |
||||
|
KScopedSchedulerLock sl(kernel); |
||||
|
|
||||
|
if (cur_thread->IsWaitingForAddressArbiter()) { |
||||
|
thread_tree.erase(thread_tree.iterator_to(*cur_thread)); |
||||
|
cur_thread->ClearAddressArbiter(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Get the result.
|
||||
|
KSynchronizationObject* dummy{}; |
||||
|
return cur_thread->GetWaitResult(std::addressof(dummy)); |
||||
|
} |
||||
|
|
||||
|
} // namespace Kernel
|
||||
@ -0,0 +1,70 @@ |
|||||
|
// Copyright 2021 yuzu Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "common/assert.h" |
||||
|
#include "common/common_types.h" |
||||
|
#include "core/hle/kernel/k_condition_variable.h" |
||||
|
#include "core/hle/kernel/svc_types.h" |
||||
|
|
||||
|
union ResultCode; |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace Kernel { |
||||
|
|
||||
|
class KernelCore; |
||||
|
|
||||
|
class KAddressArbiter { |
||||
|
public: |
||||
|
using ThreadTree = KConditionVariable::ThreadTree; |
||||
|
|
||||
|
explicit KAddressArbiter(Core::System& system_); |
||||
|
~KAddressArbiter(); |
||||
|
|
||||
|
[[nodiscard]] ResultCode SignalToAddress(VAddr addr, Svc::SignalType type, s32 value, |
||||
|
s32 count) { |
||||
|
switch (type) { |
||||
|
case Svc::SignalType::Signal: |
||||
|
return Signal(addr, count); |
||||
|
case Svc::SignalType::SignalAndIncrementIfEqual: |
||||
|
return SignalAndIncrementIfEqual(addr, value, count); |
||||
|
case Svc::SignalType::SignalAndModifyByWaitingCountIfEqual: |
||||
|
return SignalAndModifyByWaitingCountIfEqual(addr, value, count); |
||||
|
} |
||||
|
UNREACHABLE(); |
||||
|
return RESULT_UNKNOWN; |
||||
|
} |
||||
|
|
||||
|
[[nodiscard]] ResultCode WaitForAddress(VAddr addr, Svc::ArbitrationType type, s32 value, |
||||
|
s64 timeout) { |
||||
|
switch (type) { |
||||
|
case Svc::ArbitrationType::WaitIfLessThan: |
||||
|
return WaitIfLessThan(addr, value, false, timeout); |
||||
|
case Svc::ArbitrationType::DecrementAndWaitIfLessThan: |
||||
|
return WaitIfLessThan(addr, value, true, timeout); |
||||
|
case Svc::ArbitrationType::WaitIfEqual: |
||||
|
return WaitIfEqual(addr, value, timeout); |
||||
|
} |
||||
|
UNREACHABLE(); |
||||
|
return RESULT_UNKNOWN; |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
[[nodiscard]] ResultCode Signal(VAddr addr, s32 count); |
||||
|
[[nodiscard]] ResultCode SignalAndIncrementIfEqual(VAddr addr, s32 value, s32 count); |
||||
|
[[nodiscard]] ResultCode SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32 value, s32 count); |
||||
|
[[nodiscard]] ResultCode WaitIfLessThan(VAddr addr, s32 value, bool decrement, s64 timeout); |
||||
|
[[nodiscard]] ResultCode WaitIfEqual(VAddr addr, s32 value, s64 timeout); |
||||
|
|
||||
|
ThreadTree thread_tree; |
||||
|
|
||||
|
Core::System& system; |
||||
|
KernelCore& kernel; |
||||
|
}; |
||||
|
|
||||
|
} // namespace Kernel |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue