Merge pull request #370 from Subv/sync_primitives
Kernel: Reworked the new kernel synchronization primitives.
This commit is contained in:
commit
0214351f4f
|
@ -42,8 +42,6 @@ add_library(core STATIC
|
||||||
hle/kernel/client_port.h
|
hle/kernel/client_port.h
|
||||||
hle/kernel/client_session.cpp
|
hle/kernel/client_session.cpp
|
||||||
hle/kernel/client_session.h
|
hle/kernel/client_session.h
|
||||||
hle/kernel/condition_variable.cpp
|
|
||||||
hle/kernel/condition_variable.h
|
|
||||||
hle/kernel/errors.h
|
hle/kernel/errors.h
|
||||||
hle/kernel/event.cpp
|
hle/kernel/event.cpp
|
||||||
hle/kernel/event.h
|
hle/kernel/event.h
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
// Copyright 2018 yuzu emulator team
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include "common/assert.h"
|
|
||||||
#include "core/hle/kernel/condition_variable.h"
|
|
||||||
#include "core/hle/kernel/errors.h"
|
|
||||||
#include "core/hle/kernel/kernel.h"
|
|
||||||
#include "core/hle/kernel/object_address_table.h"
|
|
||||||
#include "core/hle/kernel/thread.h"
|
|
||||||
|
|
||||||
namespace Kernel {
|
|
||||||
|
|
||||||
ConditionVariable::ConditionVariable() {}
|
|
||||||
ConditionVariable::~ConditionVariable() {}
|
|
||||||
|
|
||||||
ResultVal<SharedPtr<ConditionVariable>> ConditionVariable::Create(VAddr guest_addr,
|
|
||||||
std::string name) {
|
|
||||||
SharedPtr<ConditionVariable> condition_variable(new ConditionVariable);
|
|
||||||
|
|
||||||
condition_variable->name = std::move(name);
|
|
||||||
condition_variable->guest_addr = guest_addr;
|
|
||||||
condition_variable->mutex_addr = 0;
|
|
||||||
|
|
||||||
// Condition variables are referenced by guest address, so track this in the kernel
|
|
||||||
g_object_address_table.Insert(guest_addr, condition_variable);
|
|
||||||
|
|
||||||
return MakeResult<SharedPtr<ConditionVariable>>(std::move(condition_variable));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConditionVariable::ShouldWait(Thread* thread) const {
|
|
||||||
return GetAvailableCount() <= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConditionVariable::Acquire(Thread* thread) {
|
|
||||||
if (GetAvailableCount() <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
SetAvailableCount(GetAvailableCount() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
ResultCode ConditionVariable::Release(s32 target) {
|
|
||||||
if (target == -1) {
|
|
||||||
// When -1, wake up all waiting threads
|
|
||||||
SetAvailableCount(static_cast<s32>(GetWaitingThreads().size()));
|
|
||||||
WakeupAllWaitingThreads();
|
|
||||||
} else {
|
|
||||||
// Otherwise, wake up just a single thread
|
|
||||||
SetAvailableCount(target);
|
|
||||||
WakeupWaitingThread(GetHighestPriorityReadyThread());
|
|
||||||
}
|
|
||||||
|
|
||||||
return RESULT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 ConditionVariable::GetAvailableCount() const {
|
|
||||||
return Memory::Read32(guest_addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConditionVariable::SetAvailableCount(s32 value) const {
|
|
||||||
Memory::Write32(guest_addr, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Kernel
|
|
|
@ -1,63 +0,0 @@
|
||||||
// Copyright 2018 yuzu emulator team
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <queue>
|
|
||||||
#include "common/common_types.h"
|
|
||||||
#include "core/hle/kernel/kernel.h"
|
|
||||||
#include "core/hle/kernel/wait_object.h"
|
|
||||||
#include "core/hle/result.h"
|
|
||||||
|
|
||||||
namespace Kernel {
|
|
||||||
|
|
||||||
class ConditionVariable final : public WaitObject {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Creates a condition variable.
|
|
||||||
* @param guest_addr Address of the object tracking the condition variable in guest memory. If
|
|
||||||
* specified, this condition variable will update the guest object when its state changes.
|
|
||||||
* @param name Optional name of condition variable.
|
|
||||||
* @return The created condition variable.
|
|
||||||
*/
|
|
||||||
static ResultVal<SharedPtr<ConditionVariable>> Create(VAddr guest_addr,
|
|
||||||
std::string name = "Unknown");
|
|
||||||
|
|
||||||
std::string GetTypeName() const override {
|
|
||||||
return "ConditionVariable";
|
|
||||||
}
|
|
||||||
std::string GetName() const override {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const HandleType HANDLE_TYPE = HandleType::ConditionVariable;
|
|
||||||
HandleType GetHandleType() const override {
|
|
||||||
return HANDLE_TYPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 GetAvailableCount() const;
|
|
||||||
void SetAvailableCount(s32 value) const;
|
|
||||||
|
|
||||||
std::string name; ///< Name of condition variable (optional)
|
|
||||||
VAddr guest_addr; ///< Address of the guest condition variable value
|
|
||||||
VAddr mutex_addr; ///< (optional) Address of guest mutex value associated with this condition
|
|
||||||
///< variable, used for implementing events
|
|
||||||
|
|
||||||
bool ShouldWait(Thread* thread) const override;
|
|
||||||
void Acquire(Thread* thread) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Releases a slot from a condition variable.
|
|
||||||
* @param target The number of threads to wakeup, -1 is all.
|
|
||||||
* @return ResultCode indicating if the operation succeeded.
|
|
||||||
*/
|
|
||||||
ResultCode Release(s32 target);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ConditionVariable();
|
|
||||||
~ConditionVariable() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Kernel
|
|
|
@ -20,6 +20,7 @@ enum {
|
||||||
MaxConnectionsReached = 52,
|
MaxConnectionsReached = 52,
|
||||||
|
|
||||||
// Confirmed Switch OS error codes
|
// Confirmed Switch OS error codes
|
||||||
|
MisalignedAddress = 102,
|
||||||
InvalidHandle = 114,
|
InvalidHandle = 114,
|
||||||
Timeout = 117,
|
Timeout = 117,
|
||||||
SynchronizationCanceled = 118,
|
SynchronizationCanceled = 118,
|
||||||
|
|
|
@ -18,12 +18,10 @@ using Handle = u32;
|
||||||
enum class HandleType : u32 {
|
enum class HandleType : u32 {
|
||||||
Unknown,
|
Unknown,
|
||||||
Event,
|
Event,
|
||||||
Mutex,
|
|
||||||
SharedMemory,
|
SharedMemory,
|
||||||
Thread,
|
Thread,
|
||||||
Process,
|
Process,
|
||||||
AddressArbiter,
|
AddressArbiter,
|
||||||
ConditionVariable,
|
|
||||||
Timer,
|
Timer,
|
||||||
ResourceLimit,
|
ResourceLimit,
|
||||||
CodeSet,
|
CodeSet,
|
||||||
|
@ -63,9 +61,7 @@ public:
|
||||||
bool IsWaitable() const {
|
bool IsWaitable() const {
|
||||||
switch (GetHandleType()) {
|
switch (GetHandleType()) {
|
||||||
case HandleType::Event:
|
case HandleType::Event:
|
||||||
case HandleType::Mutex:
|
|
||||||
case HandleType::Thread:
|
case HandleType::Thread:
|
||||||
case HandleType::ConditionVariable:
|
|
||||||
case HandleType::Timer:
|
case HandleType::Timer:
|
||||||
case HandleType::ServerPort:
|
case HandleType::ServerPort:
|
||||||
case HandleType::ServerSession:
|
case HandleType::ServerSession:
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <boost/range/algorithm_ext/erase.hpp>
|
#include <boost/range/algorithm_ext/erase.hpp>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/hle/kernel/errors.h"
|
||||||
#include "core/hle/kernel/handle_table.h"
|
#include "core/hle/kernel/handle_table.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
#include "core/hle/kernel/mutex.h"
|
#include "core/hle/kernel/mutex.h"
|
||||||
|
@ -15,124 +16,120 @@
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
void ReleaseThreadMutexes(Thread* thread) {
|
/// Returns the number of threads that are waiting for a mutex, and the highest priority one among
|
||||||
for (auto& mtx : thread->held_mutexes) {
|
/// those.
|
||||||
mtx->SetHasWaiters(false);
|
static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
|
||||||
mtx->SetHoldingThread(nullptr);
|
SharedPtr<Thread> current_thread, VAddr mutex_addr) {
|
||||||
mtx->WakeupAllWaitingThreads();
|
|
||||||
|
SharedPtr<Thread> highest_priority_thread;
|
||||||
|
u32 num_waiters = 0;
|
||||||
|
|
||||||
|
for (auto& thread : current_thread->wait_mutex_threads) {
|
||||||
|
if (thread->mutex_wait_address != mutex_addr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
|
||||||
|
|
||||||
|
++num_waiters;
|
||||||
|
if (highest_priority_thread == nullptr ||
|
||||||
|
thread->GetPriority() < highest_priority_thread->GetPriority()) {
|
||||||
|
highest_priority_thread = thread;
|
||||||
}
|
}
|
||||||
thread->held_mutexes.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Mutex::Mutex() {}
|
return {highest_priority_thread, num_waiters};
|
||||||
Mutex::~Mutex() {}
|
|
||||||
|
|
||||||
SharedPtr<Mutex> Mutex::Create(SharedPtr<Kernel::Thread> holding_thread, VAddr guest_addr,
|
|
||||||
std::string name) {
|
|
||||||
SharedPtr<Mutex> mutex(new Mutex);
|
|
||||||
|
|
||||||
mutex->guest_addr = guest_addr;
|
|
||||||
mutex->name = std::move(name);
|
|
||||||
|
|
||||||
// If mutex was initialized with a holding thread, acquire it by the holding thread
|
|
||||||
if (holding_thread) {
|
|
||||||
mutex->Acquire(holding_thread.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutexes are referenced by guest address, so track this in the kernel
|
/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner.
|
||||||
g_object_address_table.Insert(guest_addr, mutex);
|
static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread,
|
||||||
|
SharedPtr<Thread> new_owner) {
|
||||||
|
auto threads = current_thread->wait_mutex_threads;
|
||||||
|
for (auto& thread : threads) {
|
||||||
|
if (thread->mutex_wait_address != mutex_addr)
|
||||||
|
continue;
|
||||||
|
|
||||||
return mutex;
|
ASSERT(thread->lock_owner == current_thread);
|
||||||
|
current_thread->RemoveMutexWaiter(thread);
|
||||||
|
if (new_owner != thread)
|
||||||
|
new_owner->AddMutexWaiter(thread);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Mutex::ShouldWait(Thread* thread) const {
|
ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||||
auto holding_thread = GetHoldingThread();
|
Handle requesting_thread_handle) {
|
||||||
return holding_thread != nullptr && thread != holding_thread;
|
// The mutex address must be 4-byte aligned
|
||||||
|
if ((address % sizeof(u32)) != 0) {
|
||||||
|
return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mutex::Acquire(Thread* thread) {
|
SharedPtr<Thread> holding_thread = g_handle_table.Get<Thread>(holding_thread_handle);
|
||||||
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
|
SharedPtr<Thread> requesting_thread = g_handle_table.Get<Thread>(requesting_thread_handle);
|
||||||
|
|
||||||
priority = thread->current_priority;
|
// TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of another
|
||||||
thread->held_mutexes.insert(this);
|
// thread.
|
||||||
SetHoldingThread(thread);
|
ASSERT(requesting_thread == GetCurrentThread());
|
||||||
thread->UpdatePriority();
|
|
||||||
Core::System::GetInstance().PrepareReschedule();
|
u32 addr_value = Memory::Read32(address);
|
||||||
|
|
||||||
|
// If the mutex isn't being held, just return success.
|
||||||
|
if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) {
|
||||||
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode Mutex::Release(Thread* thread) {
|
if (holding_thread == nullptr)
|
||||||
auto holding_thread = GetHoldingThread();
|
return ERR_INVALID_HANDLE;
|
||||||
ASSERT(holding_thread);
|
|
||||||
|
|
||||||
// We can only release the mutex if it's held by the calling thread.
|
// Wait until the mutex is released
|
||||||
ASSERT(thread == holding_thread);
|
GetCurrentThread()->mutex_wait_address = address;
|
||||||
|
GetCurrentThread()->wait_handle = requesting_thread_handle;
|
||||||
|
|
||||||
|
GetCurrentThread()->status = THREADSTATUS_WAIT_MUTEX;
|
||||||
|
GetCurrentThread()->wakeup_callback = nullptr;
|
||||||
|
|
||||||
|
// Update the lock holder thread's priority to prevent priority inversion.
|
||||||
|
holding_thread->AddMutexWaiter(GetCurrentThread());
|
||||||
|
|
||||||
holding_thread->held_mutexes.erase(this);
|
|
||||||
holding_thread->UpdatePriority();
|
|
||||||
SetHoldingThread(nullptr);
|
|
||||||
SetHasWaiters(!GetWaitingThreads().empty());
|
|
||||||
WakeupAllWaitingThreads();
|
|
||||||
Core::System::GetInstance().PrepareReschedule();
|
Core::System::GetInstance().PrepareReschedule();
|
||||||
|
|
||||||
return RESULT_SUCCESS;
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mutex::AddWaitingThread(SharedPtr<Thread> thread) {
|
ResultCode Mutex::Release(VAddr address) {
|
||||||
WaitObject::AddWaitingThread(thread);
|
// The mutex address must be 4-byte aligned
|
||||||
thread->pending_mutexes.insert(this);
|
if ((address % sizeof(u32)) != 0) {
|
||||||
SetHasWaiters(true);
|
return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress);
|
||||||
UpdatePriority();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mutex::RemoveWaitingThread(Thread* thread) {
|
auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address);
|
||||||
WaitObject::RemoveWaitingThread(thread);
|
|
||||||
thread->pending_mutexes.erase(this);
|
// There are no more threads waiting for the mutex, release it completely.
|
||||||
if (!GetHasWaiters())
|
if (thread == nullptr) {
|
||||||
SetHasWaiters(!GetWaitingThreads().empty());
|
ASSERT(GetCurrentThread()->wait_mutex_threads.empty());
|
||||||
UpdatePriority();
|
Memory::Write32(address, 0);
|
||||||
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mutex::UpdatePriority() {
|
// Transfer the ownership of the mutex from the previous owner to the new one.
|
||||||
if (!GetHoldingThread())
|
TransferMutexOwnership(address, GetCurrentThread(), thread);
|
||||||
return;
|
|
||||||
|
|
||||||
u32 best_priority = THREADPRIO_LOWEST;
|
u32 mutex_value = thread->wait_handle;
|
||||||
for (auto& waiter : GetWaitingThreads()) {
|
|
||||||
if (waiter->current_priority < best_priority)
|
if (num_waiters >= 2) {
|
||||||
best_priority = waiter->current_priority;
|
// Notify the guest that there are still some threads waiting for the mutex
|
||||||
|
mutex_value |= Mutex::MutexHasWaitersFlag;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (best_priority != priority) {
|
// Grant the mutex to the next waiting thread and resume it.
|
||||||
priority = best_priority;
|
Memory::Write32(address, mutex_value);
|
||||||
GetHoldingThread()->UpdatePriority();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Handle Mutex::GetOwnerHandle() const {
|
ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
|
||||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
thread->ResumeFromWait();
|
||||||
return guest_state.holding_thread_handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedPtr<Thread> Mutex::GetHoldingThread() const {
|
thread->lock_owner = nullptr;
|
||||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
thread->condvar_wait_address = 0;
|
||||||
return g_handle_table.Get<Thread>(guest_state.holding_thread_handle);
|
thread->mutex_wait_address = 0;
|
||||||
}
|
thread->wait_handle = 0;
|
||||||
|
|
||||||
void Mutex::SetHoldingThread(SharedPtr<Thread> thread) {
|
return RESULT_SUCCESS;
|
||||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
|
||||||
guest_state.holding_thread_handle.Assign(thread ? thread->guest_handle : 0);
|
|
||||||
Memory::Write32(guest_addr, guest_state.raw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Mutex::GetHasWaiters() const {
|
|
||||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
|
||||||
return guest_state.has_waiters != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mutex::SetHasWaiters(bool has_waiters) {
|
|
||||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
|
||||||
guest_state.has_waiters.Assign(has_waiters ? 1 : 0);
|
|
||||||
Memory::Write32(guest_addr, guest_state.raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|
|
@ -15,87 +15,23 @@ namespace Kernel {
|
||||||
|
|
||||||
class Thread;
|
class Thread;
|
||||||
|
|
||||||
class Mutex final : public WaitObject {
|
class Mutex final {
|
||||||
public:
|
public:
|
||||||
/**
|
/// Flag that indicates that a mutex still has threads waiting for it.
|
||||||
* Creates a mutex.
|
static constexpr u32 MutexHasWaitersFlag = 0x40000000;
|
||||||
* @param holding_thread Specifies a thread already holding the mutex. If not nullptr, this
|
/// Mask of the bits in a mutex address value that contain the mutex owner.
|
||||||
* thread will acquire the mutex.
|
static constexpr u32 MutexOwnerMask = 0xBFFFFFFF;
|
||||||
* @param guest_addr Address of the object tracking the mutex in guest memory. If specified,
|
|
||||||
* this mutex will update the guest object when its state changes.
|
|
||||||
* @param name Optional name of mutex
|
|
||||||
* @return Pointer to new Mutex object
|
|
||||||
*/
|
|
||||||
static SharedPtr<Mutex> Create(SharedPtr<Kernel::Thread> holding_thread, VAddr guest_addr = 0,
|
|
||||||
std::string name = "Unknown");
|
|
||||||
|
|
||||||
std::string GetTypeName() const override {
|
/// Attempts to acquire a mutex at the specified address.
|
||||||
return "Mutex";
|
static ResultCode TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||||
}
|
Handle requesting_thread_handle);
|
||||||
std::string GetName() const override {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const HandleType HANDLE_TYPE = HandleType::Mutex;
|
/// Releases the mutex at the specified address.
|
||||||
HandleType GetHandleType() const override {
|
static ResultCode Release(VAddr address);
|
||||||
return HANDLE_TYPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 priority; ///< The priority of the mutex, used for priority inheritance.
|
|
||||||
std::string name; ///< Name of mutex (optional)
|
|
||||||
VAddr guest_addr; ///< Address of the guest mutex value
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Elevate the mutex priority to the best priority
|
|
||||||
* among the priorities of all its waiting threads.
|
|
||||||
*/
|
|
||||||
void UpdatePriority();
|
|
||||||
|
|
||||||
bool ShouldWait(Thread* thread) const override;
|
|
||||||
void Acquire(Thread* thread) override;
|
|
||||||
|
|
||||||
void AddWaitingThread(SharedPtr<Thread> thread) override;
|
|
||||||
void RemoveWaitingThread(Thread* thread) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to release the mutex from the specified thread.
|
|
||||||
* @param thread Thread that wants to release the mutex.
|
|
||||||
* @returns The result code of the operation.
|
|
||||||
*/
|
|
||||||
ResultCode Release(Thread* thread);
|
|
||||||
|
|
||||||
/// Gets the handle to the holding process stored in the guest state.
|
|
||||||
Handle GetOwnerHandle() const;
|
|
||||||
|
|
||||||
/// Gets the Thread pointed to by the owner handle
|
|
||||||
SharedPtr<Thread> GetHoldingThread() const;
|
|
||||||
/// Sets the holding process handle in the guest state.
|
|
||||||
void SetHoldingThread(SharedPtr<Thread> thread);
|
|
||||||
|
|
||||||
/// Returns the has_waiters bit in the guest state.
|
|
||||||
bool GetHasWaiters() const;
|
|
||||||
/// Sets the has_waiters bit in the guest state.
|
|
||||||
void SetHasWaiters(bool has_waiters);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Mutex();
|
Mutex() = default;
|
||||||
~Mutex() override;
|
~Mutex() = default;
|
||||||
|
|
||||||
/// Object in guest memory used to track the mutex state
|
|
||||||
union GuestState {
|
|
||||||
u32_le raw;
|
|
||||||
/// Handle of the thread that currently holds the mutex, 0 if available
|
|
||||||
BitField<0, 30, u32_le> holding_thread_handle;
|
|
||||||
/// 1 when there are threads waiting for this mutex, otherwise 0
|
|
||||||
BitField<30, 1, u32_le> has_waiters;
|
|
||||||
};
|
};
|
||||||
static_assert(sizeof(GuestState) == 4, "GuestState size is incorrect");
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Releases all the mutexes held by the specified thread
|
|
||||||
* @param thread Thread that is holding the mutexes
|
|
||||||
*/
|
|
||||||
void ReleaseThreadMutexes(Thread* thread);
|
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
#include "core/hle/kernel/client_port.h"
|
#include "core/hle/kernel/client_port.h"
|
||||||
#include "core/hle/kernel/client_session.h"
|
#include "core/hle/kernel/client_session.h"
|
||||||
#include "core/hle/kernel/condition_variable.h"
|
|
||||||
#include "core/hle/kernel/event.h"
|
#include "core/hle/kernel/event.h"
|
||||||
#include "core/hle/kernel/handle_table.h"
|
#include "core/hle/kernel/handle_table.h"
|
||||||
#include "core/hle/kernel/mutex.h"
|
#include "core/hle/kernel/mutex.h"
|
||||||
|
@ -262,32 +261,14 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
|
||||||
"requesting_current_thread_handle=0x%08X",
|
"requesting_current_thread_handle=0x%08X",
|
||||||
holding_thread_handle, mutex_addr, requesting_thread_handle);
|
holding_thread_handle, mutex_addr, requesting_thread_handle);
|
||||||
|
|
||||||
SharedPtr<Thread> holding_thread = g_handle_table.Get<Thread>(holding_thread_handle);
|
return Mutex::TryAcquire(mutex_addr, holding_thread_handle, requesting_thread_handle);
|
||||||
SharedPtr<Thread> requesting_thread = g_handle_table.Get<Thread>(requesting_thread_handle);
|
|
||||||
|
|
||||||
ASSERT(requesting_thread);
|
|
||||||
ASSERT(requesting_thread == GetCurrentThread());
|
|
||||||
|
|
||||||
SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr);
|
|
||||||
if (!mutex) {
|
|
||||||
// Create a new mutex for the specified address if one does not already exist
|
|
||||||
mutex = Mutex::Create(holding_thread, mutex_addr);
|
|
||||||
mutex->name = Common::StringFromFormat("mutex-%llx", mutex_addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT(holding_thread == mutex->GetHoldingThread());
|
|
||||||
|
|
||||||
return WaitSynchronization1(mutex, requesting_thread.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unlock a mutex
|
/// Unlock a mutex
|
||||||
static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
|
static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
|
||||||
LOG_TRACE(Kernel_SVC, "called mutex_addr=0x%llx", mutex_addr);
|
LOG_TRACE(Kernel_SVC, "called mutex_addr=0x%llx", mutex_addr);
|
||||||
|
|
||||||
SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr);
|
return Mutex::Release(mutex_addr);
|
||||||
ASSERT(mutex);
|
|
||||||
|
|
||||||
return mutex->Release(GetCurrentThread());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Break program execution
|
/// Break program execution
|
||||||
|
@ -412,11 +393,6 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
|
||||||
}
|
}
|
||||||
|
|
||||||
thread->SetPriority(priority);
|
thread->SetPriority(priority);
|
||||||
thread->UpdatePriority();
|
|
||||||
|
|
||||||
// Update the mutexes that this thread is waiting for
|
|
||||||
for (auto& mutex : thread->pending_mutexes)
|
|
||||||
mutex->UpdatePriority();
|
|
||||||
|
|
||||||
Core::System::GetInstance().PrepareReschedule();
|
Core::System::GetInstance().PrepareReschedule();
|
||||||
return RESULT_SUCCESS;
|
return RESULT_SUCCESS;
|
||||||
|
@ -634,77 +610,20 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
|
||||||
SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
|
SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
|
||||||
ASSERT(thread);
|
ASSERT(thread);
|
||||||
|
|
||||||
SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr);
|
CASCADE_CODE(Mutex::Release(mutex_addr));
|
||||||
if (!mutex) {
|
|
||||||
// Create a new mutex for the specified address if one does not already exist
|
|
||||||
mutex = Mutex::Create(thread, mutex_addr);
|
|
||||||
mutex->name = Common::StringFromFormat("mutex-%llx", mutex_addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedPtr<ConditionVariable> condition_variable =
|
SharedPtr<Thread> current_thread = GetCurrentThread();
|
||||||
g_object_address_table.Get<ConditionVariable>(condition_variable_addr);
|
current_thread->condvar_wait_address = condition_variable_addr;
|
||||||
if (!condition_variable) {
|
current_thread->mutex_wait_address = mutex_addr;
|
||||||
// Create a new condition_variable for the specified address if one does not already exist
|
current_thread->wait_handle = thread_handle;
|
||||||
condition_variable = ConditionVariable::Create(condition_variable_addr).Unwrap();
|
current_thread->status = THREADSTATUS_WAIT_MUTEX;
|
||||||
condition_variable->name =
|
current_thread->wakeup_callback = nullptr;
|
||||||
Common::StringFromFormat("condition-variable-%llx", condition_variable_addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (condition_variable->mutex_addr) {
|
current_thread->WakeAfterDelay(nano_seconds);
|
||||||
// Previously created the ConditionVariable using WaitProcessWideKeyAtomic, verify
|
|
||||||
// everything is correct
|
|
||||||
ASSERT(condition_variable->mutex_addr == mutex_addr);
|
|
||||||
} else {
|
|
||||||
// Previously created the ConditionVariable using SignalProcessWideKey, set the mutex
|
|
||||||
// associated with it
|
|
||||||
condition_variable->mutex_addr = mutex_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mutex->GetOwnerHandle()) {
|
// Note: Deliberately don't attempt to inherit the lock owner's priority.
|
||||||
// Release the mutex if the current thread is holding it
|
|
||||||
mutex->Release(thread.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto wakeup_callback = [mutex, nano_seconds](ThreadWakeupReason reason,
|
|
||||||
SharedPtr<Thread> thread,
|
|
||||||
SharedPtr<WaitObject> object, size_t index) {
|
|
||||||
ASSERT(thread->status == THREADSTATUS_WAIT_SYNCH_ANY);
|
|
||||||
|
|
||||||
if (reason == ThreadWakeupReason::Timeout) {
|
|
||||||
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT(reason == ThreadWakeupReason::Signal);
|
|
||||||
|
|
||||||
// Now try to acquire the mutex and don't resume if it's not available.
|
|
||||||
if (!mutex->ShouldWait(thread.get())) {
|
|
||||||
mutex->Acquire(thread.get());
|
|
||||||
thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nano_seconds == 0) {
|
|
||||||
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
thread->wait_objects = {mutex};
|
|
||||||
mutex->AddWaitingThread(thread);
|
|
||||||
thread->status = THREADSTATUS_WAIT_SYNCH_ANY;
|
|
||||||
|
|
||||||
// Create an event to wake the thread up after the
|
|
||||||
// specified nanosecond delay has passed
|
|
||||||
thread->WakeAfterDelay(nano_seconds);
|
|
||||||
thread->wakeup_callback = DefaultThreadWakeupCallback;
|
|
||||||
|
|
||||||
Core::System::GetInstance().PrepareReschedule();
|
Core::System::GetInstance().PrepareReschedule();
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
CASCADE_CODE(
|
|
||||||
WaitSynchronization1(condition_variable, thread.get(), nano_seconds, wakeup_callback));
|
|
||||||
|
|
||||||
return RESULT_SUCCESS;
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -713,24 +632,53 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||||
LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x%llx, target=0x%08x",
|
LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x%llx, target=0x%08x",
|
||||||
condition_variable_addr, target);
|
condition_variable_addr, target);
|
||||||
|
|
||||||
// Wakeup all or one thread - Any other value is unimplemented
|
u32 processed = 0;
|
||||||
ASSERT(target == -1 || target == 1);
|
auto& thread_list = Core::System::GetInstance().Scheduler().GetThreadList();
|
||||||
|
|
||||||
SharedPtr<ConditionVariable> condition_variable =
|
for (auto& thread : thread_list) {
|
||||||
g_object_address_table.Get<ConditionVariable>(condition_variable_addr);
|
if (thread->condvar_wait_address != condition_variable_addr)
|
||||||
if (!condition_variable) {
|
continue;
|
||||||
// Create a new condition_variable for the specified address if one does not already exist
|
|
||||||
condition_variable = ConditionVariable::Create(condition_variable_addr).Unwrap();
|
// Only process up to 'target' threads, unless 'target' is -1, in which case process
|
||||||
condition_variable->name =
|
// them all.
|
||||||
Common::StringFromFormat("condition-variable-%llx", condition_variable_addr);
|
if (target != -1 && processed >= target)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// If the mutex is not yet acquired, acquire it.
|
||||||
|
u32 mutex_val = Memory::Read32(thread->mutex_wait_address);
|
||||||
|
|
||||||
|
if (mutex_val == 0) {
|
||||||
|
// We were able to acquire the mutex, resume this thread.
|
||||||
|
Memory::Write32(thread->mutex_wait_address, thread->wait_handle);
|
||||||
|
ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
|
||||||
|
thread->ResumeFromWait();
|
||||||
|
|
||||||
|
auto lock_owner = thread->lock_owner;
|
||||||
|
if (lock_owner)
|
||||||
|
lock_owner->RemoveMutexWaiter(thread);
|
||||||
|
|
||||||
|
thread->lock_owner = nullptr;
|
||||||
|
thread->mutex_wait_address = 0;
|
||||||
|
thread->condvar_wait_address = 0;
|
||||||
|
thread->wait_handle = 0;
|
||||||
|
} else {
|
||||||
|
// Couldn't acquire the mutex, block the thread.
|
||||||
|
Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
|
||||||
|
auto owner = g_handle_table.Get<Thread>(owner_handle);
|
||||||
|
ASSERT(owner);
|
||||||
|
ASSERT(thread->status != THREADSTATUS_RUNNING);
|
||||||
|
thread->status = THREADSTATUS_WAIT_MUTEX;
|
||||||
|
thread->wakeup_callback = nullptr;
|
||||||
|
|
||||||
|
// Signal that the mutex now has a waiting thread.
|
||||||
|
Memory::Write32(thread->mutex_wait_address, mutex_val | Mutex::MutexHasWaitersFlag);
|
||||||
|
|
||||||
|
owner->AddMutexWaiter(thread);
|
||||||
|
|
||||||
|
Core::System::GetInstance().PrepareReschedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
CASCADE_CODE(condition_variable->Release(target));
|
++processed;
|
||||||
|
|
||||||
if (condition_variable->mutex_addr) {
|
|
||||||
// If a mutex was created for this condition_variable, wait the current thread on it
|
|
||||||
SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(condition_variable->mutex_addr);
|
|
||||||
return WaitSynchronization1(mutex, GetCurrentThread());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return RESULT_SUCCESS;
|
return RESULT_SUCCESS;
|
||||||
|
|
|
@ -77,9 +77,6 @@ void Thread::Stop() {
|
||||||
}
|
}
|
||||||
wait_objects.clear();
|
wait_objects.clear();
|
||||||
|
|
||||||
// Release all the mutexes that this thread holds
|
|
||||||
ReleaseThreadMutexes(this);
|
|
||||||
|
|
||||||
// Mark the TLS slot in the thread's page as free.
|
// Mark the TLS slot in the thread's page as free.
|
||||||
u64 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE;
|
u64 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE;
|
||||||
u64 tls_slot =
|
u64 tls_slot =
|
||||||
|
@ -126,6 +123,19 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
|
||||||
resume = thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
|
resume = thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (thread->mutex_wait_address != 0 || thread->condvar_wait_address != 0 ||
|
||||||
|
thread->wait_handle) {
|
||||||
|
ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
|
||||||
|
thread->mutex_wait_address = 0;
|
||||||
|
thread->condvar_wait_address = 0;
|
||||||
|
thread->wait_handle = 0;
|
||||||
|
|
||||||
|
auto lock_owner = thread->lock_owner;
|
||||||
|
// Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
|
||||||
|
// and don't have a lock owner.
|
||||||
|
ASSERT(lock_owner == nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
if (resume)
|
if (resume)
|
||||||
thread->ResumeFromWait();
|
thread->ResumeFromWait();
|
||||||
}
|
}
|
||||||
|
@ -151,6 +161,7 @@ void Thread::ResumeFromWait() {
|
||||||
case THREADSTATUS_WAIT_HLE_EVENT:
|
case THREADSTATUS_WAIT_HLE_EVENT:
|
||||||
case THREADSTATUS_WAIT_SLEEP:
|
case THREADSTATUS_WAIT_SLEEP:
|
||||||
case THREADSTATUS_WAIT_IPC:
|
case THREADSTATUS_WAIT_IPC:
|
||||||
|
case THREADSTATUS_WAIT_MUTEX:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case THREADSTATUS_READY:
|
case THREADSTATUS_READY:
|
||||||
|
@ -256,7 +267,9 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
|
||||||
thread->last_running_ticks = CoreTiming::GetTicks();
|
thread->last_running_ticks = CoreTiming::GetTicks();
|
||||||
thread->processor_id = processor_id;
|
thread->processor_id = processor_id;
|
||||||
thread->wait_objects.clear();
|
thread->wait_objects.clear();
|
||||||
thread->wait_address = 0;
|
thread->mutex_wait_address = 0;
|
||||||
|
thread->condvar_wait_address = 0;
|
||||||
|
thread->wait_handle = 0;
|
||||||
thread->name = std::move(name);
|
thread->name = std::move(name);
|
||||||
thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap();
|
thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap();
|
||||||
thread->owner_process = owner_process;
|
thread->owner_process = owner_process;
|
||||||
|
@ -317,17 +330,8 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
|
||||||
void Thread::SetPriority(u32 priority) {
|
void Thread::SetPriority(u32 priority) {
|
||||||
ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
|
ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
|
||||||
"Invalid priority value.");
|
"Invalid priority value.");
|
||||||
Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority);
|
nominal_priority = priority;
|
||||||
nominal_priority = current_priority = priority;
|
UpdatePriority();
|
||||||
}
|
|
||||||
|
|
||||||
void Thread::UpdatePriority() {
|
|
||||||
u32 best_priority = nominal_priority;
|
|
||||||
for (auto& mutex : held_mutexes) {
|
|
||||||
if (mutex->priority < best_priority)
|
|
||||||
best_priority = mutex->priority;
|
|
||||||
}
|
|
||||||
BoostPriority(best_priority);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Thread::BoostPriority(u32 priority) {
|
void Thread::BoostPriority(u32 priority) {
|
||||||
|
@ -377,6 +381,38 @@ VAddr Thread::GetCommandBufferAddress() const {
|
||||||
return GetTLSAddress() + CommandHeaderOffset;
|
return GetTLSAddress() + CommandHeaderOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
|
||||||
|
thread->lock_owner = this;
|
||||||
|
wait_mutex_threads.emplace_back(std::move(thread));
|
||||||
|
UpdatePriority();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) {
|
||||||
|
boost::remove_erase(wait_mutex_threads, thread);
|
||||||
|
thread->lock_owner = nullptr;
|
||||||
|
UpdatePriority();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Thread::UpdatePriority() {
|
||||||
|
// Find the highest priority among all the threads that are waiting for this thread's lock
|
||||||
|
u32 new_priority = nominal_priority;
|
||||||
|
for (const auto& thread : wait_mutex_threads) {
|
||||||
|
if (thread->nominal_priority < new_priority)
|
||||||
|
new_priority = thread->nominal_priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_priority == current_priority)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Core::System::GetInstance().Scheduler().SetThreadPriority(this, new_priority);
|
||||||
|
|
||||||
|
current_priority = new_priority;
|
||||||
|
|
||||||
|
// Recursively update the priority of the thread that depends on the priority of this one.
|
||||||
|
if (lock_owner)
|
||||||
|
lock_owner->UpdatePriority();
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
enum ThreadPriority : u32 {
|
enum ThreadPriority : u32 {
|
||||||
THREADPRIO_HIGHEST = 0, ///< Highest thread priority
|
THREADPRIO_HIGHEST = 0, ///< Highest thread priority
|
||||||
THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
|
THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
|
||||||
THREADPRIO_DEFAULT = 48, ///< Default thread priority for userland apps
|
THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps
|
||||||
THREADPRIO_LOWEST = 63, ///< Lowest thread priority
|
THREADPRIO_LOWEST = 63, ///< Lowest thread priority
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ enum ThreadStatus {
|
||||||
THREADSTATUS_WAIT_IPC, ///< Waiting for the reply from an IPC request
|
THREADSTATUS_WAIT_IPC, ///< Waiting for the reply from an IPC request
|
||||||
THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false
|
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_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true
|
||||||
|
THREADSTATUS_WAIT_MUTEX, ///< Waiting due to an ArbitrateLock/WaitProcessWideKey svc
|
||||||
THREADSTATUS_DORMANT, ///< Created but not yet made ready
|
THREADSTATUS_DORMANT, ///< Created but not yet made ready
|
||||||
THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated
|
THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated
|
||||||
};
|
};
|
||||||
|
@ -54,7 +55,6 @@ enum class ThreadWakeupReason {
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
class Mutex;
|
|
||||||
class Process;
|
class Process;
|
||||||
|
|
||||||
class Thread final : public WaitObject {
|
class Thread final : public WaitObject {
|
||||||
|
@ -103,18 +103,21 @@ public:
|
||||||
*/
|
*/
|
||||||
void SetPriority(u32 priority);
|
void SetPriority(u32 priority);
|
||||||
|
|
||||||
/**
|
|
||||||
* Boost's a thread's priority to the best priority among the thread's held mutexes.
|
|
||||||
* This prevents priority inversion via priority inheritance.
|
|
||||||
*/
|
|
||||||
void UpdatePriority();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temporarily boosts the thread's priority until the next time it is scheduled
|
* Temporarily boosts the thread's priority until the next time it is scheduled
|
||||||
* @param priority The new priority
|
* @param priority The new priority
|
||||||
*/
|
*/
|
||||||
void BoostPriority(u32 priority);
|
void BoostPriority(u32 priority);
|
||||||
|
|
||||||
|
/// Adds a thread to the list of threads that are waiting for a lock held by this thread.
|
||||||
|
void AddMutexWaiter(SharedPtr<Thread> thread);
|
||||||
|
|
||||||
|
/// Removes a thread from the list of threads that are waiting for a lock held by this thread.
|
||||||
|
void RemoveMutexWaiter(SharedPtr<Thread> thread);
|
||||||
|
|
||||||
|
/// Recalculates the current priority taking into account priority inheritance.
|
||||||
|
void UpdatePriority();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the thread's thread ID
|
* Gets the thread's thread ID
|
||||||
* @return The thread's ID
|
* @return The thread's ID
|
||||||
|
@ -205,19 +208,22 @@ public:
|
||||||
|
|
||||||
VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread
|
VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread
|
||||||
|
|
||||||
/// Mutexes currently held by this thread, which will be released when it exits.
|
|
||||||
boost::container::flat_set<SharedPtr<Mutex>> held_mutexes;
|
|
||||||
|
|
||||||
/// Mutexes that this thread is currently waiting for.
|
|
||||||
boost::container::flat_set<SharedPtr<Mutex>> pending_mutexes;
|
|
||||||
|
|
||||||
SharedPtr<Process> owner_process; ///< Process that owns this thread
|
SharedPtr<Process> owner_process; ///< Process that owns this thread
|
||||||
|
|
||||||
/// Objects that the thread is waiting on, in the same order as they were
|
/// Objects that the thread is waiting on, in the same order as they were
|
||||||
// passed to WaitSynchronization1/N.
|
// passed to WaitSynchronization1/N.
|
||||||
std::vector<SharedPtr<WaitObject>> wait_objects;
|
std::vector<SharedPtr<WaitObject>> wait_objects;
|
||||||
|
|
||||||
VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address
|
/// List of threads that are waiting for a mutex that is held by this thread.
|
||||||
|
std::vector<SharedPtr<Thread>> wait_mutex_threads;
|
||||||
|
|
||||||
|
/// Thread that owns the lock that this thread is waiting for.
|
||||||
|
SharedPtr<Thread> lock_owner;
|
||||||
|
|
||||||
|
// If waiting on a ConditionVariable, this is the ConditionVariable address
|
||||||
|
VAddr condvar_wait_address;
|
||||||
|
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.
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
#include "core/hle/service/nvflinger/buffer_queue.h"
|
#include "core/hle/service/nvflinger/buffer_queue.h"
|
||||||
|
|
||||||
namespace Service::NVFlinger {
|
namespace Service {
|
||||||
|
namespace NVFlinger {
|
||||||
|
|
||||||
BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) {
|
BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) {
|
||||||
native_handle = Kernel::Event::Create(Kernel::ResetType::OneShot, "BufferQueue NativeHandle");
|
native_handle = Kernel::Event::Create(Kernel::ResetType::OneShot, "BufferQueue NativeHandle");
|
||||||
|
@ -110,4 +111,5 @@ void BufferQueue::SetBufferWaitEvent(Kernel::SharedPtr<Kernel::Event>&& wait_eve
|
||||||
buffer_wait_event = std::move(wait_event);
|
buffer_wait_event = std::move(wait_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Service::NVFlinger
|
} // namespace NVFlinger
|
||||||
|
} // namespace Service
|
||||||
|
|
|
@ -13,7 +13,8 @@ namespace CoreTiming {
|
||||||
struct EventType;
|
struct EventType;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Service::NVFlinger {
|
namespace Service {
|
||||||
|
namespace NVFlinger {
|
||||||
|
|
||||||
struct IGBPBuffer {
|
struct IGBPBuffer {
|
||||||
u32_le magic;
|
u32_le magic;
|
||||||
|
@ -97,4 +98,5 @@ private:
|
||||||
Kernel::SharedPtr<Kernel::Event> buffer_wait_event;
|
Kernel::SharedPtr<Kernel::Event> buffer_wait_event;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::NVFlinger
|
} // namespace NVFlinger
|
||||||
|
} // namespace Service
|
||||||
|
|
|
@ -137,7 +137,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||||
process->address_mappings = default_address_mappings;
|
process->address_mappings = default_address_mappings;
|
||||||
process->resource_limit =
|
process->resource_limit =
|
||||||
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
|
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
|
||||||
process->Run(base_addr, 48, Memory::DEFAULT_STACK_SIZE);
|
process->Run(base_addr, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
|
||||||
|
|
||||||
is_loaded = true;
|
is_loaded = true;
|
||||||
return ResultStatus::Success;
|
return ResultStatus::Success;
|
||||||
|
|
|
@ -165,7 +165,7 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||||
process->address_mappings = default_address_mappings;
|
process->address_mappings = default_address_mappings;
|
||||||
process->resource_limit =
|
process->resource_limit =
|
||||||
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
|
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
|
||||||
process->Run(Memory::PROCESS_IMAGE_VADDR, 48, Memory::DEFAULT_STACK_SIZE);
|
process->Run(Memory::PROCESS_IMAGE_VADDR, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
|
||||||
|
|
||||||
is_loaded = true;
|
is_loaded = true;
|
||||||
return ResultStatus::Success;
|
return ResultStatus::Success;
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
#include "yuzu/util/util.h"
|
#include "yuzu/util/util.h"
|
||||||
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hle/kernel/condition_variable.h"
|
|
||||||
#include "core/hle/kernel/event.h"
|
#include "core/hle/kernel/event.h"
|
||||||
|
#include "core/hle/kernel/handle_table.h"
|
||||||
#include "core/hle/kernel/mutex.h"
|
#include "core/hle/kernel/mutex.h"
|
||||||
#include "core/hle/kernel/thread.h"
|
#include "core/hle/kernel/thread.h"
|
||||||
#include "core/hle/kernel/timer.h"
|
#include "core/hle/kernel/timer.h"
|
||||||
|
@ -67,6 +67,29 @@ QString WaitTreeText::GetText() const {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address) : mutex_address(mutex_address) {
|
||||||
|
mutex_value = Memory::Read32(mutex_address);
|
||||||
|
owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Mutex::MutexOwnerMask);
|
||||||
|
owner = Kernel::g_handle_table.Get<Kernel::Thread>(owner_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WaitTreeMutexInfo::GetText() const {
|
||||||
|
return tr("waiting for mutex 0x%1").arg(mutex_address, 16, 16, QLatin1Char('0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() const {
|
||||||
|
std::vector<std::unique_ptr<WaitTreeItem>> list;
|
||||||
|
|
||||||
|
bool has_waiters = (mutex_value & Kernel::Mutex::MutexHasWaitersFlag) != 0;
|
||||||
|
|
||||||
|
list.push_back(std::make_unique<WaitTreeText>(tr("has waiters: %1").arg(has_waiters)));
|
||||||
|
list.push_back(std::make_unique<WaitTreeText>(
|
||||||
|
tr("owner handle: 0x%1").arg(owner_handle, 8, 16, QLatin1Char('0'))));
|
||||||
|
if (owner != nullptr)
|
||||||
|
list.push_back(std::make_unique<WaitTreeThread>(*owner));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
WaitTreeWaitObject::WaitTreeWaitObject(const Kernel::WaitObject& o) : object(o) {}
|
WaitTreeWaitObject::WaitTreeWaitObject(const Kernel::WaitObject& o) : object(o) {}
|
||||||
|
|
||||||
bool WaitTreeExpandableItem::IsExpandable() const {
|
bool WaitTreeExpandableItem::IsExpandable() const {
|
||||||
|
@ -84,11 +107,6 @@ std::unique_ptr<WaitTreeWaitObject> WaitTreeWaitObject::make(const Kernel::WaitO
|
||||||
switch (object.GetHandleType()) {
|
switch (object.GetHandleType()) {
|
||||||
case Kernel::HandleType::Event:
|
case Kernel::HandleType::Event:
|
||||||
return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::Event&>(object));
|
return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::Event&>(object));
|
||||||
case Kernel::HandleType::Mutex:
|
|
||||||
return std::make_unique<WaitTreeMutex>(static_cast<const Kernel::Mutex&>(object));
|
|
||||||
case Kernel::HandleType::ConditionVariable:
|
|
||||||
return std::make_unique<WaitTreeConditionVariable>(
|
|
||||||
static_cast<const Kernel::ConditionVariable&>(object));
|
|
||||||
case Kernel::HandleType::Timer:
|
case Kernel::HandleType::Timer:
|
||||||
return std::make_unique<WaitTreeTimer>(static_cast<const Kernel::Timer&>(object));
|
return std::make_unique<WaitTreeTimer>(static_cast<const Kernel::Timer&>(object));
|
||||||
case Kernel::HandleType::Thread:
|
case Kernel::HandleType::Thread:
|
||||||
|
@ -160,6 +178,9 @@ QString WaitTreeThread::GetText() const {
|
||||||
case THREADSTATUS_WAIT_SYNCH_ANY:
|
case THREADSTATUS_WAIT_SYNCH_ANY:
|
||||||
status = tr("waiting for objects");
|
status = tr("waiting for objects");
|
||||||
break;
|
break;
|
||||||
|
case THREADSTATUS_WAIT_MUTEX:
|
||||||
|
status = tr("waiting for mutex");
|
||||||
|
break;
|
||||||
case THREADSTATUS_DORMANT:
|
case THREADSTATUS_DORMANT:
|
||||||
status = tr("dormant");
|
status = tr("dormant");
|
||||||
break;
|
break;
|
||||||
|
@ -186,6 +207,7 @@ QColor WaitTreeThread::GetColor() const {
|
||||||
return QColor(Qt::GlobalColor::darkYellow);
|
return QColor(Qt::GlobalColor::darkYellow);
|
||||||
case THREADSTATUS_WAIT_SYNCH_ALL:
|
case THREADSTATUS_WAIT_SYNCH_ALL:
|
||||||
case THREADSTATUS_WAIT_SYNCH_ANY:
|
case THREADSTATUS_WAIT_SYNCH_ANY:
|
||||||
|
case THREADSTATUS_WAIT_MUTEX:
|
||||||
return QColor(Qt::GlobalColor::red);
|
return QColor(Qt::GlobalColor::red);
|
||||||
case THREADSTATUS_DORMANT:
|
case THREADSTATUS_DORMANT:
|
||||||
return QColor(Qt::GlobalColor::darkCyan);
|
return QColor(Qt::GlobalColor::darkCyan);
|
||||||
|
@ -225,11 +247,11 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
|
||||||
list.push_back(std::make_unique<WaitTreeText>(
|
list.push_back(std::make_unique<WaitTreeText>(
|
||||||
tr("last running ticks = %1").arg(thread.last_running_ticks)));
|
tr("last running ticks = %1").arg(thread.last_running_ticks)));
|
||||||
|
|
||||||
if (thread.held_mutexes.empty()) {
|
if (thread.mutex_wait_address != 0)
|
||||||
list.push_back(std::make_unique<WaitTreeText>(tr("not holding mutex")));
|
list.push_back(std::make_unique<WaitTreeMutexInfo>(thread.mutex_wait_address));
|
||||||
} else {
|
else
|
||||||
list.push_back(std::make_unique<WaitTreeMutexList>(thread.held_mutexes));
|
list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex")));
|
||||||
}
|
|
||||||
if (thread.status == THREADSTATUS_WAIT_SYNCH_ANY ||
|
if (thread.status == THREADSTATUS_WAIT_SYNCH_ANY ||
|
||||||
thread.status == THREADSTATUS_WAIT_SYNCH_ALL) {
|
thread.status == THREADSTATUS_WAIT_SYNCH_ALL) {
|
||||||
list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects,
|
list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects,
|
||||||
|
@ -250,33 +272,6 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeEvent::GetChildren() const {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
WaitTreeMutex::WaitTreeMutex(const Kernel::Mutex& object) : WaitTreeWaitObject(object) {}
|
|
||||||
|
|
||||||
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutex::GetChildren() const {
|
|
||||||
std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
|
|
||||||
|
|
||||||
const auto& mutex = static_cast<const Kernel::Mutex&>(object);
|
|
||||||
if (mutex.GetHasWaiters()) {
|
|
||||||
list.push_back(std::make_unique<WaitTreeText>(tr("locked by thread:")));
|
|
||||||
list.push_back(std::make_unique<WaitTreeThread>(*mutex.GetHoldingThread()));
|
|
||||||
} else {
|
|
||||||
list.push_back(std::make_unique<WaitTreeText>(tr("free")));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitTreeConditionVariable::WaitTreeConditionVariable(const Kernel::ConditionVariable& object)
|
|
||||||
: WaitTreeWaitObject(object) {}
|
|
||||||
|
|
||||||
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeConditionVariable::GetChildren() const {
|
|
||||||
std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
|
|
||||||
|
|
||||||
const auto& condition_variable = static_cast<const Kernel::ConditionVariable&>(object);
|
|
||||||
list.push_back(std::make_unique<WaitTreeText>(
|
|
||||||
tr("available count = %1").arg(condition_variable.GetAvailableCount())));
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitTreeTimer::WaitTreeTimer(const Kernel::Timer& object) : WaitTreeWaitObject(object) {}
|
WaitTreeTimer::WaitTreeTimer(const Kernel::Timer& object) : WaitTreeWaitObject(object) {}
|
||||||
|
|
||||||
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const {
|
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const {
|
||||||
|
@ -293,21 +288,6 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
WaitTreeMutexList::WaitTreeMutexList(
|
|
||||||
const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& list)
|
|
||||||
: mutex_list(list) {}
|
|
||||||
|
|
||||||
QString WaitTreeMutexList::GetText() const {
|
|
||||||
return tr("holding mutexes");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexList::GetChildren() const {
|
|
||||||
std::vector<std::unique_ptr<WaitTreeItem>> list(mutex_list.size());
|
|
||||||
std::transform(mutex_list.begin(), mutex_list.end(), list.begin(),
|
|
||||||
[](const auto& t) { return std::make_unique<WaitTreeMutex>(*t); });
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitTreeThreadList::WaitTreeThreadList(const std::vector<Kernel::SharedPtr<Kernel::Thread>>& list)
|
WaitTreeThreadList::WaitTreeThreadList(const std::vector<Kernel::SharedPtr<Kernel::Thread>>& list)
|
||||||
: thread_list(list) {}
|
: thread_list(list) {}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,6 @@ class EmuThread;
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
class WaitObject;
|
class WaitObject;
|
||||||
class Event;
|
class Event;
|
||||||
class Mutex;
|
|
||||||
class ConditionVariable;
|
|
||||||
class Thread;
|
class Thread;
|
||||||
class Timer;
|
class Timer;
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
@ -61,6 +59,20 @@ public:
|
||||||
bool IsExpandable() const override;
|
bool IsExpandable() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class WaitTreeMutexInfo : public WaitTreeExpandableItem {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit WaitTreeMutexInfo(VAddr mutex_address);
|
||||||
|
QString GetText() const override;
|
||||||
|
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
VAddr mutex_address;
|
||||||
|
u32 mutex_value;
|
||||||
|
Kernel::Handle owner_handle;
|
||||||
|
Kernel::SharedPtr<Kernel::Thread> owner;
|
||||||
|
};
|
||||||
|
|
||||||
class WaitTreeWaitObject : public WaitTreeExpandableItem {
|
class WaitTreeWaitObject : public WaitTreeExpandableItem {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
@ -104,20 +116,6 @@ public:
|
||||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WaitTreeMutex : public WaitTreeWaitObject {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit WaitTreeMutex(const Kernel::Mutex& object);
|
|
||||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class WaitTreeConditionVariable : public WaitTreeWaitObject {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit WaitTreeConditionVariable(const Kernel::ConditionVariable& object);
|
|
||||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class WaitTreeTimer : public WaitTreeWaitObject {
|
class WaitTreeTimer : public WaitTreeWaitObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
@ -125,19 +123,6 @@ public:
|
||||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WaitTreeMutexList : public WaitTreeExpandableItem {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit WaitTreeMutexList(
|
|
||||||
const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& list);
|
|
||||||
|
|
||||||
QString GetText() const override;
|
|
||||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& mutex_list;
|
|
||||||
};
|
|
||||||
|
|
||||||
class WaitTreeThreadList : public WaitTreeExpandableItem {
|
class WaitTreeThreadList : public WaitTreeExpandableItem {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|
Reference in New Issue