mirror of
https://github.com/Lime3DS/Lime3DS
synced 2024-12-27 09:32:30 -06:00
Merge pull request #849 from bunnei/fix-waitsynch-2
Fix svcWaitSynch to correctly acquire wait objects
This commit is contained in:
commit
2e16edbc56
9 changed files with 68 additions and 113 deletions
|
@ -9,11 +9,15 @@
|
|||
#include "core/arm/arm_interface.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/hle/hle.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace HLE {
|
||||
|
||||
#define PARAM(n) Core::g_app_core->GetReg(n)
|
||||
|
||||
/// An invalid result code that is meant to be overwritten when a thread resumes from waiting
|
||||
static const ResultCode RESULT_INVALID(0xDEADC0DE);
|
||||
|
||||
/**
|
||||
* HLE a function return from the current ARM11 userland process
|
||||
* @param res Result to return
|
||||
|
@ -57,8 +61,11 @@ template<ResultCode func(s32*, u32*, s32, bool, s64)> void Wrap() {
|
|||
s32 param_1 = 0;
|
||||
s32 retval = func(¶m_1, (Handle*)Memory::GetPointer(PARAM(1)), (s32)PARAM(2),
|
||||
(PARAM(3) != 0), (((s64)PARAM(4) << 32) | PARAM(0))).raw;
|
||||
Core::g_app_core->SetReg(1, (u32)param_1);
|
||||
FuncReturn(retval);
|
||||
|
||||
if (retval != RESULT_INVALID.raw) {
|
||||
Core::g_app_core->SetReg(1, (u32)param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
}
|
||||
|
||||
template<ResultCode func(u32, u32, u32, u32, s64)> void Wrap() {
|
||||
|
@ -73,7 +80,11 @@ template<ResultCode func(u32*)> void Wrap(){
|
|||
}
|
||||
|
||||
template<ResultCode func(u32, s64)> void Wrap() {
|
||||
FuncReturn(func(PARAM(0), (((s64)PARAM(3) << 32) | PARAM(2))).raw);
|
||||
s32 retval = func(PARAM(0), (((s64)PARAM(3) << 32) | PARAM(2))).raw;
|
||||
|
||||
if (retval != RESULT_INVALID.raw) {
|
||||
FuncReturn(retval);
|
||||
}
|
||||
}
|
||||
|
||||
template<ResultCode func(void*, void*, u32)> void Wrap(){
|
||||
|
|
|
@ -41,10 +41,7 @@ void Event::Acquire() {
|
|||
|
||||
void Event::Signal() {
|
||||
signaled = true;
|
||||
|
||||
WakeupAllWaitingThreads();
|
||||
|
||||
HLE::Reschedule(__func__);
|
||||
}
|
||||
|
||||
void Event::Clear() {
|
||||
|
|
|
@ -32,27 +32,13 @@ void WaitObject::RemoveWaitingThread(Thread* thread) {
|
|||
waiting_threads.erase(itr);
|
||||
}
|
||||
|
||||
SharedPtr<Thread> WaitObject::WakeupNextThread() {
|
||||
if (waiting_threads.empty())
|
||||
return nullptr;
|
||||
|
||||
auto next_thread = std::move(waiting_threads.front());
|
||||
waiting_threads.erase(waiting_threads.begin());
|
||||
|
||||
next_thread->ReleaseWaitObject(this);
|
||||
|
||||
return next_thread;
|
||||
}
|
||||
|
||||
void WaitObject::WakeupAllWaitingThreads() {
|
||||
auto waiting_threads_copy = waiting_threads;
|
||||
for (auto thread : waiting_threads)
|
||||
thread->ResumeFromWait();
|
||||
|
||||
// We use a copy because ReleaseWaitObject will remove the thread from this object's
|
||||
// waiting_threads list
|
||||
for (auto thread : waiting_threads_copy)
|
||||
thread->ReleaseWaitObject(this);
|
||||
waiting_threads.clear();
|
||||
|
||||
ASSERT_MSG(waiting_threads.empty(), "failed to awaken all waiting threads!");
|
||||
HLE::Reschedule(__func__);
|
||||
}
|
||||
|
||||
HandleTable::HandleTable() {
|
||||
|
|
|
@ -140,12 +140,6 @@ public:
|
|||
*/
|
||||
void RemoveWaitingThread(Thread* thread);
|
||||
|
||||
/**
|
||||
* Wake up the next thread waiting on this object
|
||||
* @return Pointer to the thread that was resumed, nullptr if no threads are waiting
|
||||
*/
|
||||
SharedPtr<Thread> WakeupNextThread();
|
||||
|
||||
/// Wake up all threads waiting on this object
|
||||
void WakeupAllWaitingThreads();
|
||||
|
||||
|
|
|
@ -23,12 +23,7 @@ static void ResumeWaitingThread(Mutex* mutex) {
|
|||
// Reset mutex lock thread handle, nothing is waiting
|
||||
mutex->lock_count = 0;
|
||||
mutex->holding_thread = nullptr;
|
||||
|
||||
// Find the next waiting thread for the mutex...
|
||||
auto next_thread = mutex->WakeupNextThread();
|
||||
if (next_thread != nullptr) {
|
||||
mutex->Acquire(next_thread);
|
||||
}
|
||||
mutex->WakeupAllWaitingThreads();
|
||||
}
|
||||
|
||||
void ReleaseThreadMutexes(Thread* thread) {
|
||||
|
@ -94,8 +89,6 @@ void Mutex::Release() {
|
|||
ResumeWaitingThread(this);
|
||||
}
|
||||
}
|
||||
|
||||
HLE::Reschedule(__func__);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -48,13 +48,7 @@ ResultVal<s32> Semaphore::Release(s32 release_count) {
|
|||
s32 previous_count = available_count;
|
||||
available_count += release_count;
|
||||
|
||||
// Notify some of the threads that the semaphore has been released
|
||||
// stop once the semaphore is full again or there are no more waiting threads
|
||||
while (!ShouldWait() && WakeupNextThread() != nullptr) {
|
||||
Acquire();
|
||||
}
|
||||
|
||||
HLE::Reschedule(__func__);
|
||||
WakeupAllWaitingThreads();
|
||||
|
||||
return MakeResult<s32>(previous_count);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "common/thread_queue_list.h"
|
||||
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/arm/skyeye_common/armdefs.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/hle.h"
|
||||
|
@ -193,8 +194,22 @@ static void SwitchContext(Thread* new_thread) {
|
|||
if (new_thread) {
|
||||
DEBUG_ASSERT_MSG(new_thread->status == THREADSTATUS_READY, "Thread must be ready to become running.");
|
||||
|
||||
// Cancel any outstanding wakeup events for this thread
|
||||
CoreTiming::UnscheduleEvent(ThreadWakeupEventType, new_thread->callback_handle);
|
||||
|
||||
current_thread = new_thread;
|
||||
|
||||
// If the thread was waited by a svcWaitSynch call, step back PC by one instruction to rerun
|
||||
// the SVC when the thread wakes up. This is necessary to ensure that the thread can acquire
|
||||
// the requested wait object(s) before continuing.
|
||||
if (new_thread->waitsynch_waited) {
|
||||
// CPSR flag indicates CPU mode
|
||||
bool thumb_mode = (new_thread->context.cpsr & TBIT) != 0;
|
||||
|
||||
// SVC instruction is 2 bytes for THUMB, 4 bytes for ARM
|
||||
new_thread->context.pc -= thumb_mode ? 2 : 4;
|
||||
}
|
||||
|
||||
ready_queue.remove(new_thread->current_priority, new_thread);
|
||||
new_thread->status = THREADSTATUS_RUNNING;
|
||||
|
||||
|
@ -243,6 +258,7 @@ void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wa
|
|||
thread->wait_set_output = wait_set_output;
|
||||
thread->wait_all = wait_all;
|
||||
thread->wait_objects = std::move(wait_objects);
|
||||
thread->waitsynch_waited = true;
|
||||
thread->status = THREADSTATUS_WAIT_SYNCH;
|
||||
}
|
||||
|
||||
|
@ -268,6 +284,8 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
|
|||
return;
|
||||
}
|
||||
|
||||
thread->waitsynch_waited = false;
|
||||
|
||||
if (thread->status == THREADSTATUS_WAIT_SYNCH) {
|
||||
thread->SetWaitSynchronizationResult(ResultCode(ErrorDescription::Timeout, ErrorModule::OS,
|
||||
ErrorSummary::StatusChanged, ErrorLevel::Info));
|
||||
|
@ -288,63 +306,20 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
|
|||
CoreTiming::ScheduleEvent(usToCycles(microseconds), ThreadWakeupEventType, callback_handle);
|
||||
}
|
||||
|
||||
void Thread::ReleaseWaitObject(WaitObject* wait_object) {
|
||||
if (status != THREADSTATUS_WAIT_SYNCH || wait_objects.empty()) {
|
||||
LOG_CRITICAL(Kernel, "thread is not waiting on any objects!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove this thread from the waiting object's thread list
|
||||
wait_object->RemoveWaitingThread(this);
|
||||
|
||||
unsigned index = 0;
|
||||
bool wait_all_failed = false; // Will be set to true if any object is unavailable
|
||||
|
||||
// Iterate through all waiting objects to check availability...
|
||||
for (auto itr = wait_objects.begin(); itr != wait_objects.end(); ++itr) {
|
||||
if ((*itr)->ShouldWait())
|
||||
wait_all_failed = true;
|
||||
|
||||
// The output should be the last index of wait_object
|
||||
if (*itr == wait_object)
|
||||
index = itr - wait_objects.begin();
|
||||
}
|
||||
|
||||
// If we are waiting on all objects...
|
||||
if (wait_all) {
|
||||
// Resume the thread only if all are available...
|
||||
if (!wait_all_failed) {
|
||||
SetWaitSynchronizationResult(RESULT_SUCCESS);
|
||||
SetWaitSynchronizationOutput(-1);
|
||||
|
||||
ResumeFromWait();
|
||||
}
|
||||
} else {
|
||||
// Otherwise, resume
|
||||
SetWaitSynchronizationResult(RESULT_SUCCESS);
|
||||
|
||||
if (wait_set_output)
|
||||
SetWaitSynchronizationOutput(index);
|
||||
|
||||
ResumeFromWait();
|
||||
}
|
||||
}
|
||||
|
||||
void Thread::ResumeFromWait() {
|
||||
// Cancel any outstanding wakeup events for this thread
|
||||
CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle);
|
||||
|
||||
switch (status) {
|
||||
case THREADSTATUS_WAIT_SYNCH:
|
||||
// Remove this thread from all other WaitObjects
|
||||
for (auto wait_object : wait_objects)
|
||||
wait_object->RemoveWaitingThread(this);
|
||||
break;
|
||||
case THREADSTATUS_WAIT_ARB:
|
||||
case THREADSTATUS_WAIT_SLEEP:
|
||||
break;
|
||||
case THREADSTATUS_RUNNING:
|
||||
|
||||
case THREADSTATUS_READY:
|
||||
// If the thread is waiting on multiple wait objects, it might be awoken more than once
|
||||
// before actually resuming. We can ignore subsequent wakeups if the thread status has
|
||||
// already been set to THREADSTATUS_READY.
|
||||
return;
|
||||
|
||||
case THREADSTATUS_RUNNING:
|
||||
DEBUG_ASSERT_MSG(false, "Thread with object id %u has already resumed.", GetObjectId());
|
||||
return;
|
||||
case THREADSTATUS_DEAD:
|
||||
|
@ -415,6 +390,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
|
|||
thread->callback_handle = wakeup_callback_handle_table.Create(thread).MoveFrom();
|
||||
thread->owner_process = g_current_process;
|
||||
thread->tls_index = -1;
|
||||
thread->waitsynch_waited = false;
|
||||
|
||||
// Find the next available TLS index, and mark it as used
|
||||
auto& used_tls_slots = Kernel::g_current_process->used_tls_slots;
|
||||
|
|
|
@ -95,12 +95,6 @@ public:
|
|||
*/
|
||||
u32 GetThreadId() const { return thread_id; }
|
||||
|
||||
/**
|
||||
* Release an acquired wait object
|
||||
* @param wait_object WaitObject to release
|
||||
*/
|
||||
void ReleaseWaitObject(WaitObject* wait_object);
|
||||
|
||||
/**
|
||||
* Resumes a thread from waiting
|
||||
*/
|
||||
|
@ -152,6 +146,8 @@ public:
|
|||
|
||||
s32 tls_index; ///< Index of the Thread Local Storage of the thread
|
||||
|
||||
bool waitsynch_waited; ///< Set to true if the last svcWaitSynch call caused the thread to wait
|
||||
|
||||
/// Mutexes currently held by this thread, which will be released when it exits.
|
||||
boost::container::flat_set<SharedPtr<Mutex>> held_mutexes;
|
||||
|
||||
|
@ -163,12 +159,12 @@ public:
|
|||
|
||||
std::string name;
|
||||
|
||||
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
|
||||
Handle callback_handle;
|
||||
|
||||
private:
|
||||
Thread();
|
||||
~Thread() override;
|
||||
|
||||
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
|
||||
Handle callback_handle;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -40,9 +40,6 @@ const ResultCode ERR_NOT_FOUND(ErrorDescription::NotFound, ErrorModule::Kernel,
|
|||
const ResultCode ERR_PORT_NAME_TOO_LONG(ErrorDescription(30), ErrorModule::OS,
|
||||
ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E0181E
|
||||
|
||||
/// An invalid result code that is meant to be overwritten when a thread resumes from waiting
|
||||
const ResultCode RESULT_INVALID(0xDEADC0DE);
|
||||
|
||||
enum ControlMemoryOperation {
|
||||
MEMORY_OPERATION_HEAP = 0x00000003,
|
||||
MEMORY_OPERATION_GSP_HEAP = 0x00010003,
|
||||
|
@ -143,6 +140,10 @@ static ResultCode CloseHandle(Handle handle) {
|
|||
/// Wait for a handle to synchronize, timeout after the specified nanoseconds
|
||||
static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) {
|
||||
auto object = Kernel::g_handle_table.GetWaitObject(handle);
|
||||
Kernel::Thread* thread = Kernel::GetCurrentThread();
|
||||
|
||||
thread->waitsynch_waited = false;
|
||||
|
||||
if (object == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
|
@ -154,14 +155,14 @@ static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) {
|
|||
// Check for next thread to schedule
|
||||
if (object->ShouldWait()) {
|
||||
|
||||
object->AddWaitingThread(Kernel::GetCurrentThread());
|
||||
object->AddWaitingThread(thread);
|
||||
Kernel::WaitCurrentThread_WaitSynchronization({ object }, false, false);
|
||||
|
||||
// Create an event to wake the thread up after the specified nanosecond delay has passed
|
||||
Kernel::GetCurrentThread()->WakeAfterDelay(nano_seconds);
|
||||
thread->WakeAfterDelay(nano_seconds);
|
||||
|
||||
// NOTE: output of this SVC will be set later depending on how the thread resumes
|
||||
return RESULT_INVALID;
|
||||
return HLE::RESULT_INVALID;
|
||||
}
|
||||
|
||||
object->Acquire();
|
||||
|
@ -173,6 +174,9 @@ static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) {
|
|||
static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_count, bool wait_all, s64 nano_seconds) {
|
||||
bool wait_thread = !wait_all;
|
||||
int handle_index = 0;
|
||||
Kernel::Thread* thread = Kernel::GetCurrentThread();
|
||||
bool was_waiting = thread->waitsynch_waited;
|
||||
thread->waitsynch_waited = false;
|
||||
|
||||
// Check if 'handles' is invalid
|
||||
if (handles == nullptr)
|
||||
|
@ -190,6 +194,9 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou
|
|||
// necessary
|
||||
if (handle_count != 0) {
|
||||
bool selected = false; // True once an object has been selected
|
||||
|
||||
Kernel::SharedPtr<Kernel::WaitObject> wait_object;
|
||||
|
||||
for (int i = 0; i < handle_count; ++i) {
|
||||
auto object = Kernel::g_handle_table.GetWaitObject(handles[i]);
|
||||
if (object == nullptr)
|
||||
|
@ -204,10 +211,11 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou
|
|||
wait_thread = true;
|
||||
} else {
|
||||
// Do not wait on this object, check if this object should be selected...
|
||||
if (!wait_all && !selected) {
|
||||
if (!wait_all && (!selected || (wait_object == object && was_waiting))) {
|
||||
// Do not wait the thread
|
||||
wait_thread = false;
|
||||
handle_index = i;
|
||||
wait_object = object;
|
||||
selected = true;
|
||||
}
|
||||
}
|
||||
|
@ -241,7 +249,7 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou
|
|||
Kernel::GetCurrentThread()->WakeAfterDelay(nano_seconds);
|
||||
|
||||
// NOTE: output of this SVC will be set later depending on how the thread resumes
|
||||
return RESULT_INVALID;
|
||||
return HLE::RESULT_INVALID;
|
||||
}
|
||||
|
||||
// Acquire objects if we did not wait...
|
||||
|
@ -261,7 +269,7 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou
|
|||
|
||||
// TODO(bunnei): If 'wait_all' is true, this is probably wrong. However, real hardware does
|
||||
// not seem to set it to any meaningful value.
|
||||
*out = wait_all ? 0 : handle_index;
|
||||
*out = handle_count != 0 ? (wait_all ? -1 : handle_index) : 0;
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue