Merge pull request #849 from bunnei/fix-waitsynch-2

Fix svcWaitSynch to correctly acquire wait objects
merge-requests/60/head
bunnei 2015-06-17 18:07:15 +07:00
commit 2e16edbc56
9 changed files with 68 additions and 113 deletions

@ -9,11 +9,15 @@
#include "core/arm/arm_interface.h" #include "core/arm/arm_interface.h"
#include "core/memory.h" #include "core/memory.h"
#include "core/hle/hle.h" #include "core/hle/hle.h"
#include "core/hle/result.h"
namespace HLE { namespace HLE {
#define PARAM(n) Core::g_app_core->GetReg(n) #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 * HLE a function return from the current ARM11 userland process
* @param res Result to return * @param res Result to return
@ -57,8 +61,11 @@ template<ResultCode func(s32*, u32*, s32, bool, s64)> void Wrap() {
s32 param_1 = 0; s32 param_1 = 0;
s32 retval = func(&param_1, (Handle*)Memory::GetPointer(PARAM(1)), (s32)PARAM(2), s32 retval = func(&param_1, (Handle*)Memory::GetPointer(PARAM(1)), (s32)PARAM(2),
(PARAM(3) != 0), (((s64)PARAM(4) << 32) | PARAM(0))).raw; (PARAM(3) != 0), (((s64)PARAM(4) << 32) | PARAM(0))).raw;
if (retval != RESULT_INVALID.raw) {
Core::g_app_core->SetReg(1, (u32)param_1); Core::g_app_core->SetReg(1, (u32)param_1);
FuncReturn(retval); FuncReturn(retval);
}
} }
template<ResultCode func(u32, u32, u32, u32, s64)> void Wrap() { 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() { 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(){ template<ResultCode func(void*, void*, u32)> void Wrap(){

@ -41,10 +41,7 @@ void Event::Acquire() {
void Event::Signal() { void Event::Signal() {
signaled = true; signaled = true;
WakeupAllWaitingThreads(); WakeupAllWaitingThreads();
HLE::Reschedule(__func__);
} }
void Event::Clear() { void Event::Clear() {

@ -32,27 +32,13 @@ void WaitObject::RemoveWaitingThread(Thread* thread) {
waiting_threads.erase(itr); 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() { 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.clear();
// waiting_threads list
for (auto thread : waiting_threads_copy)
thread->ReleaseWaitObject(this);
ASSERT_MSG(waiting_threads.empty(), "failed to awaken all waiting threads!"); HLE::Reschedule(__func__);
} }
HandleTable::HandleTable() { HandleTable::HandleTable() {

@ -140,12 +140,6 @@ public:
*/ */
void RemoveWaitingThread(Thread* thread); 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 /// Wake up all threads waiting on this object
void WakeupAllWaitingThreads(); void WakeupAllWaitingThreads();

@ -23,12 +23,7 @@ static void ResumeWaitingThread(Mutex* mutex) {
// Reset mutex lock thread handle, nothing is waiting // Reset mutex lock thread handle, nothing is waiting
mutex->lock_count = 0; mutex->lock_count = 0;
mutex->holding_thread = nullptr; mutex->holding_thread = nullptr;
mutex->WakeupAllWaitingThreads();
// Find the next waiting thread for the mutex...
auto next_thread = mutex->WakeupNextThread();
if (next_thread != nullptr) {
mutex->Acquire(next_thread);
}
} }
void ReleaseThreadMutexes(Thread* thread) { void ReleaseThreadMutexes(Thread* thread) {
@ -94,8 +89,6 @@ void Mutex::Release() {
ResumeWaitingThread(this); ResumeWaitingThread(this);
} }
} }
HLE::Reschedule(__func__);
} }
} // namespace } // namespace

@ -48,13 +48,7 @@ ResultVal<s32> Semaphore::Release(s32 release_count) {
s32 previous_count = available_count; s32 previous_count = available_count;
available_count += release_count; available_count += release_count;
// Notify some of the threads that the semaphore has been released WakeupAllWaitingThreads();
// stop once the semaphore is full again or there are no more waiting threads
while (!ShouldWait() && WakeupNextThread() != nullptr) {
Acquire();
}
HLE::Reschedule(__func__);
return MakeResult<s32>(previous_count); return MakeResult<s32>(previous_count);
} }

@ -13,6 +13,7 @@
#include "common/thread_queue_list.h" #include "common/thread_queue_list.h"
#include "core/arm/arm_interface.h" #include "core/arm/arm_interface.h"
#include "core/arm/skyeye_common/armdefs.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/hle/hle.h" #include "core/hle/hle.h"
@ -193,8 +194,22 @@ static void SwitchContext(Thread* new_thread) {
if (new_thread) { if (new_thread) {
DEBUG_ASSERT_MSG(new_thread->status == THREADSTATUS_READY, "Thread must be ready to become running."); 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; 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); ready_queue.remove(new_thread->current_priority, new_thread);
new_thread->status = THREADSTATUS_RUNNING; 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_set_output = wait_set_output;
thread->wait_all = wait_all; thread->wait_all = wait_all;
thread->wait_objects = std::move(wait_objects); thread->wait_objects = std::move(wait_objects);
thread->waitsynch_waited = true;
thread->status = THREADSTATUS_WAIT_SYNCH; thread->status = THREADSTATUS_WAIT_SYNCH;
} }
@ -268,6 +284,8 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
return; return;
} }
thread->waitsynch_waited = false;
if (thread->status == THREADSTATUS_WAIT_SYNCH) { if (thread->status == THREADSTATUS_WAIT_SYNCH) {
thread->SetWaitSynchronizationResult(ResultCode(ErrorDescription::Timeout, ErrorModule::OS, thread->SetWaitSynchronizationResult(ResultCode(ErrorDescription::Timeout, ErrorModule::OS,
ErrorSummary::StatusChanged, ErrorLevel::Info)); ErrorSummary::StatusChanged, ErrorLevel::Info));
@ -288,63 +306,20 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
CoreTiming::ScheduleEvent(usToCycles(microseconds), ThreadWakeupEventType, callback_handle); 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() { void Thread::ResumeFromWait() {
// Cancel any outstanding wakeup events for this thread
CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle);
switch (status) { switch (status) {
case THREADSTATUS_WAIT_SYNCH: 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_ARB:
case THREADSTATUS_WAIT_SLEEP: case THREADSTATUS_WAIT_SLEEP:
break; break;
case THREADSTATUS_RUNNING:
case THREADSTATUS_READY: 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()); DEBUG_ASSERT_MSG(false, "Thread with object id %u has already resumed.", GetObjectId());
return; return;
case THREADSTATUS_DEAD: 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->callback_handle = wakeup_callback_handle_table.Create(thread).MoveFrom();
thread->owner_process = g_current_process; thread->owner_process = g_current_process;
thread->tls_index = -1; thread->tls_index = -1;
thread->waitsynch_waited = false;
// Find the next available TLS index, and mark it as used // Find the next available TLS index, and mark it as used
auto& used_tls_slots = Kernel::g_current_process->used_tls_slots; auto& used_tls_slots = Kernel::g_current_process->used_tls_slots;

@ -95,12 +95,6 @@ public:
*/ */
u32 GetThreadId() const { return thread_id; } 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 * Resumes a thread from waiting
*/ */
@ -152,6 +146,8 @@ public:
s32 tls_index; ///< Index of the Thread Local Storage of the thread 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. /// Mutexes currently held by this thread, which will be released when it exits.
boost::container::flat_set<SharedPtr<Mutex>> held_mutexes; boost::container::flat_set<SharedPtr<Mutex>> held_mutexes;
@ -163,12 +159,12 @@ public:
std::string name; std::string name;
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
Handle callback_handle;
private: private:
Thread(); Thread();
~Thread() override; ~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, const ResultCode ERR_PORT_NAME_TOO_LONG(ErrorDescription(30), ErrorModule::OS,
ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E0181E 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 { enum ControlMemoryOperation {
MEMORY_OPERATION_HEAP = 0x00000003, MEMORY_OPERATION_HEAP = 0x00000003,
MEMORY_OPERATION_GSP_HEAP = 0x00010003, 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 /// Wait for a handle to synchronize, timeout after the specified nanoseconds
static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) { static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) {
auto object = Kernel::g_handle_table.GetWaitObject(handle); auto object = Kernel::g_handle_table.GetWaitObject(handle);
Kernel::Thread* thread = Kernel::GetCurrentThread();
thread->waitsynch_waited = false;
if (object == nullptr) if (object == nullptr)
return ERR_INVALID_HANDLE; return ERR_INVALID_HANDLE;
@ -154,14 +155,14 @@ static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) {
// Check for next thread to schedule // Check for next thread to schedule
if (object->ShouldWait()) { if (object->ShouldWait()) {
object->AddWaitingThread(Kernel::GetCurrentThread()); object->AddWaitingThread(thread);
Kernel::WaitCurrentThread_WaitSynchronization({ object }, false, false); Kernel::WaitCurrentThread_WaitSynchronization({ object }, false, false);
// Create an event to wake the thread up after the specified nanosecond delay has passed // 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 // NOTE: output of this SVC will be set later depending on how the thread resumes
return RESULT_INVALID; return HLE::RESULT_INVALID;
} }
object->Acquire(); 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) { static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_count, bool wait_all, s64 nano_seconds) {
bool wait_thread = !wait_all; bool wait_thread = !wait_all;
int handle_index = 0; int handle_index = 0;
Kernel::Thread* thread = Kernel::GetCurrentThread();
bool was_waiting = thread->waitsynch_waited;
thread->waitsynch_waited = false;
// Check if 'handles' is invalid // Check if 'handles' is invalid
if (handles == nullptr) if (handles == nullptr)
@ -190,6 +194,9 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou
// necessary // necessary
if (handle_count != 0) { if (handle_count != 0) {
bool selected = false; // True once an object has been selected bool selected = false; // True once an object has been selected
Kernel::SharedPtr<Kernel::WaitObject> wait_object;
for (int i = 0; i < handle_count; ++i) { for (int i = 0; i < handle_count; ++i) {
auto object = Kernel::g_handle_table.GetWaitObject(handles[i]); auto object = Kernel::g_handle_table.GetWaitObject(handles[i]);
if (object == nullptr) if (object == nullptr)
@ -204,10 +211,11 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou
wait_thread = true; wait_thread = true;
} else { } else {
// Do not wait on this object, check if this object should be selected... // 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 // Do not wait the thread
wait_thread = false; wait_thread = false;
handle_index = i; handle_index = i;
wait_object = object;
selected = true; selected = true;
} }
} }
@ -241,7 +249,7 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou
Kernel::GetCurrentThread()->WakeAfterDelay(nano_seconds); Kernel::GetCurrentThread()->WakeAfterDelay(nano_seconds);
// NOTE: output of this SVC will be set later depending on how the thread resumes // 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... // 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 // TODO(bunnei): If 'wait_all' is true, this is probably wrong. However, real hardware does
// not seem to set it to any meaningful value. // 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; return RESULT_SUCCESS;
} }