diff --git a/rpcs3/Emu/SysCalls/Modules/sysPrxForUser.cpp b/rpcs3/Emu/SysCalls/Modules/sysPrxForUser.cpp index 9c33f83c00..556bb0d6c4 100644 --- a/rpcs3/Emu/SysCalls/Modules/sysPrxForUser.cpp +++ b/rpcs3/Emu/SysCalls/Modules/sysPrxForUser.cpp @@ -217,7 +217,7 @@ s32 sys_lwmutex_lock(PPUThread& ppu, vm::ptr lwmutex, u64 timeout } // lock using the syscall - const s32 res = _sys_lwmutex_lock(lwmutex->sleep_queue, timeout); + const s32 res = _sys_lwmutex_lock(ppu, lwmutex->sleep_queue, timeout); lwmutex->all_info--; diff --git a/rpcs3/Emu/SysCalls/lv2/sys_lwcond.cpp b/rpcs3/Emu/SysCalls/lv2/sys_lwcond.cpp index a0abe0632f..8f9b52585a 100644 --- a/rpcs3/Emu/SysCalls/lv2/sys_lwcond.cpp +++ b/rpcs3/Emu/SysCalls/lv2/sys_lwcond.cpp @@ -5,8 +5,6 @@ #include "Emu/SysCalls/SysCalls.h" #include "Emu/Cell/PPUThread.h" -#include "Emu/SysCalls/Modules/sysPrxForUser.h" -#include "sleep_queue.h" #include "sys_lwmutex.h" #include "sys_lwcond.h" @@ -14,6 +12,30 @@ SysCallBase sys_lwcond("sys_lwcond"); extern u64 get_system_time(); +void lv2_lwcond_t::notify(lv2_lock_t & lv2_lock, sleep_queue_t::iterator it, const std::shared_ptr& mutex, bool mode2) +{ + CHECK_LV2_LOCK(lv2_lock); + + auto& ppu = static_cast(*it->get()); + + ppu.GPR[3] = mode2; // set to return CELL_EBUSY + + if (!mode2) + { + if (!mutex->signaled) + { + return mutex->sq.emplace_back(*it); + } + + mutex->signaled--; + } + + if (!ppu.signal()) + { + throw EXCEPTION("Thread already signaled"); + } +} + s32 _sys_lwcond_create(vm::ptr lwcond_id, u32 lwmutex_id, vm::ptr control, u64 name, u32 arg5) { sys_lwcond.Warning("_sys_lwcond_create(lwcond_id=*0x%x, lwmutex_id=0x%x, control=*0x%x, name=0x%llx, arg5=0x%x)", lwcond_id, lwmutex_id, control, name, arg5); @@ -36,7 +58,7 @@ s32 _sys_lwcond_destroy(u32 lwcond_id) return CELL_ESRCH; } - if (!cond->waiters.empty() || cond->signaled1 || cond->signaled2) + if (!cond->sq.empty()) { return CELL_EBUSY; } @@ -62,47 +84,39 @@ s32 _sys_lwcond_signal(u32 lwcond_id, u32 lwmutex_id, u32 ppu_thread_id, u32 mod if (mode != 1 && mode != 2 && mode != 3) { - sys_lwcond.Error("_sys_lwcond_signal(%d): invalid mode (%d)", lwcond_id, mode); + throw EXCEPTION("Unknown mode (%d)", mode); } - const auto found = ~ppu_thread_id ? cond->waiters.find(ppu_thread_id) : cond->waiters.begin(); + // mode 1: lightweight mutex was initially owned by the calling thread + // mode 2: lightweight mutex was not owned by the calling thread and waiter hasn't been increased + // mode 3: lightweight mutex was forcefully owned by the calling thread - if (mode == 1) + // pick waiter; protocol is ignored in current implementation + const auto found = !~ppu_thread_id ? cond->sq.begin() : std::find_if(cond->sq.begin(), cond->sq.end(), [=](sleep_queue_t::value_type& thread) { - // mode 1: lightweight mutex was initially owned by the calling thread + return thread->get_id() == ppu_thread_id; + }); - if (found == cond->waiters.end()) + if (found == cond->sq.end()) + { + if (mode == 1) { return CELL_EPERM; } - - cond->signaled1++; - } - else if (mode == 2) - { - // mode 2: lightweight mutex was not owned by the calling thread and waiter hasn't been increased - - if (found == cond->waiters.end()) + else if (mode == 2) { return CELL_OK; } - - cond->signaled2++; - } - else - { - // in mode 3, lightweight mutex was forcefully owned by the calling thread - - if (found == cond->waiters.end()) + else { return ~ppu_thread_id ? CELL_ENOENT : CELL_EPERM; } - - cond->signaled1++; } - cond->waiters.erase(found); - cond->cv.notify_one(); + // signal specified waiting thread + cond->notify(lv2_lock, found, mutex, mode == 2); + + cond->sq.erase(found); return CELL_OK; } @@ -123,36 +137,27 @@ s32 _sys_lwcond_signal_all(u32 lwcond_id, u32 lwmutex_id, u32 mode) if (mode != 1 && mode != 2) { - sys_lwcond.Error("_sys_lwcond_signal_all(%d): invalid mode (%d)", lwcond_id, mode); + throw EXCEPTION("Unknown mode (%d)", mode); } - const u32 count = (u32)cond->waiters.size(); + // mode 1: lightweight mutex was initially owned by the calling thread + // mode 2: lightweight mutex was not owned by the calling thread and waiter hasn't been increased - if (count) + // signal all waiting threads; protocol is ignored in current implementation + for (auto it = cond->sq.begin(); it != cond->sq.end(); it++) { - cond->waiters.clear(); - cond->cv.notify_all(); + cond->notify(lv2_lock, it, mutex, mode == 2); } - if (mode == 1) - { - // in mode 1, lightweight mutex was initially owned by the calling thread + // in mode 1, return the amount of threads signaled + const s32 result = mode == 2 ? CELL_OK : static_cast(cond->sq.size()); - cond->signaled1 += count; + cond->sq.clear(); - return count; - } - else - { - // in mode 2, lightweight mutex was not owned by the calling thread and waiter hasn't been increased - - cond->signaled2 += count; - - return CELL_OK; - } + return result; } -s32 _sys_lwcond_queue_wait(PPUThread& CPU, u32 lwcond_id, u32 lwmutex_id, u64 timeout) +s32 _sys_lwcond_queue_wait(PPUThread& ppu, u32 lwcond_id, u32 lwmutex_id, u64 timeout) { sys_lwcond.Log("_sys_lwcond_queue_wait(lwcond_id=0x%x, lwmutex_id=0x%x, timeout=0x%llx)", lwcond_id, lwmutex_id, timeout); @@ -169,64 +174,45 @@ s32 _sys_lwcond_queue_wait(PPUThread& CPU, u32 lwcond_id, u32 lwmutex_id, u64 ti } // finalize unlocking the mutex - mutex->signaled++; - - if (mutex->waiters) - { - mutex->cv.notify_one(); - } + mutex->unlock(lv2_lock); // add waiter; protocol is ignored in current implementation - cond->waiters.emplace(CPU.get_id()); + sleep_queue_entry_t waiter(ppu, cond->sq); - while ((!(cond->signaled1 && mutex->signaled) && !cond->signaled2) || cond->waiters.count(CPU.get_id())) + // potential mutex waiter (not added immediately) + sleep_queue_entry_t mutex_waiter(ppu, cond->sq, defer_sleep); + + while (!ppu.unsignal()) { CHECK_EMU_STATUS; - const bool is_timedout = timeout && get_system_time() - start_time > timeout; - - // check timeout - if (is_timedout) + if (timeout && waiter) { - // cancel waiting - if (!cond->waiters.erase(CPU.get_id())) + const u64 passed = get_system_time() - start_time; + + if (passed >= timeout) { - if (cond->signaled1 && !mutex->signaled) + // try to reown the mutex if timed out + if (mutex->signaled) { - cond->signaled1--; + mutex->signaled--; + + return CELL_EDEADLK; } else { - throw EXCEPTION("Unexpected values"); + return CELL_ETIMEDOUT; } } - if (mutex->signaled) - { - mutex->signaled--; - - return CELL_EDEADLK; - } - else - { - return CELL_ETIMEDOUT; - } + ppu.cv.wait_for(lv2_lock, std::chrono::microseconds(timeout - passed)); + } + else + { + ppu.cv.wait(lv2_lock); } - - (cond->signaled1 ? mutex->cv : cond->cv).wait_for(lv2_lock, std::chrono::milliseconds(1)); } - if (cond->signaled1 && mutex->signaled) - { - mutex->signaled--; - cond->signaled1--; - - return CELL_OK; - } - else - { - cond->signaled2--; - - return CELL_EBUSY; - } + // return cause + return ppu.GPR[3] ? CELL_EBUSY : CELL_OK; } diff --git a/rpcs3/Emu/SysCalls/lv2/sys_lwcond.h b/rpcs3/Emu/SysCalls/lv2/sys_lwcond.h index 22943ccc05..8541ec3486 100644 --- a/rpcs3/Emu/SysCalls/lv2/sys_lwcond.h +++ b/rpcs3/Emu/SysCalls/lv2/sys_lwcond.h @@ -1,5 +1,7 @@ #pragma once +#include "sleep_queue.h" + namespace vm { using namespace ps3; } struct sys_lwmutex_t; @@ -23,20 +25,14 @@ struct lv2_lwcond_t { const u64 name; - std::atomic signaled1; // mode 1 signals - std::atomic signaled2; // mode 2 signals - - // TODO: use sleep queue - std::condition_variable cv; - std::unordered_set waiters; + sleep_queue_t sq; lv2_lwcond_t(u64 name) : name(name) - , signaled1(0) - , signaled2(0) - //, waiters(0) { } + + void notify(lv2_lock_t& lv2_lock, sleep_queue_t::iterator it, const std::shared_ptr& mutex, bool mode2); }; REG_ID_TYPE(lv2_lwcond_t, 0x97); // SYS_LWCOND_OBJECT @@ -49,4 +45,4 @@ s32 _sys_lwcond_create(vm::ptr lwcond_id, u32 lwmutex_id, vm::ptrsignal()) + { + throw EXCEPTION("Thread already signaled"); + } + + sq.pop_front(); + } + else + { + signaled++; + } +} + s32 _sys_lwmutex_create(vm::ptr lwmutex_id, u32 protocol, vm::ptr control, u32 arg4, u64 name, u32 arg6) { sys_lwmutex.Warning("_sys_lwmutex_create(lwmutex_id=*0x%x, protocol=0x%x, control=*0x%x, arg4=0x%x, name=0x%llx, arg6=0x%x)", lwmutex_id, protocol, control, arg4, name, arg6); @@ -45,7 +68,7 @@ s32 _sys_lwmutex_destroy(u32 lwmutex_id) return CELL_ESRCH; } - if (mutex->waiters) + if (!mutex->sq.empty()) { return CELL_EBUSY; } @@ -55,7 +78,7 @@ s32 _sys_lwmutex_destroy(u32 lwmutex_id) return CELL_OK; } -s32 _sys_lwmutex_lock(u32 lwmutex_id, u64 timeout) +s32 _sys_lwmutex_lock(PPUThread& ppu, u32 lwmutex_id, u64 timeout) { sys_lwmutex.Log("_sys_lwmutex_lock(lwmutex_id=0x%x, timeout=0x%llx)", lwmutex_id, timeout); @@ -70,25 +93,36 @@ s32 _sys_lwmutex_lock(u32 lwmutex_id, u64 timeout) return CELL_ESRCH; } - // protocol is ignored in current implementation - mutex->waiters++; + if (mutex->signaled) + { + mutex->signaled--; - while (!mutex->signaled) + return CELL_OK; + } + + // add waiter; protocol is ignored in current implementation + sleep_queue_entry_t waiter(ppu, mutex->sq); + + while (!ppu.unsignal()) { CHECK_EMU_STATUS; - if (timeout && get_system_time() - start_time > timeout) + if (timeout) { - mutex->waiters--; - return CELL_ETIMEDOUT; + const u64 passed = get_system_time() - start_time; + + if (passed >= timeout) + { + return CELL_ETIMEDOUT; + } + + ppu.cv.wait_for(lv2_lock, std::chrono::microseconds(timeout - passed)); + } + else + { + ppu.cv.wait(lv2_lock); } - - mutex->cv.wait_for(lv2_lock, std::chrono::milliseconds(1)); } - - mutex->signaled--; - - mutex->waiters--; return CELL_OK; } @@ -106,7 +140,7 @@ s32 _sys_lwmutex_trylock(u32 lwmutex_id) return CELL_ESRCH; } - if (mutex->waiters || !mutex->signaled) + if (!mutex->sq.empty() || !mutex->signaled) { return CELL_EBUSY; } @@ -129,17 +163,7 @@ s32 _sys_lwmutex_unlock(u32 lwmutex_id) return CELL_ESRCH; } - if (mutex->signaled) - { - throw EXCEPTION("Already signaled (lwmutex_id=0x%x)", lwmutex_id); - } - - mutex->signaled++; - - if (mutex->waiters) - { - mutex->cv.notify_one(); - } + mutex->unlock(lv2_lock); return CELL_OK; } diff --git a/rpcs3/Emu/SysCalls/lv2/sys_lwmutex.h b/rpcs3/Emu/SysCalls/lv2/sys_lwmutex.h index 7412ee65af..9af8236acd 100644 --- a/rpcs3/Emu/SysCalls/lv2/sys_lwmutex.h +++ b/rpcs3/Emu/SysCalls/lv2/sys_lwmutex.h @@ -1,5 +1,7 @@ #pragma once +#include "sleep_queue.h" + namespace vm { using namespace ps3; } struct sys_lwmutex_attribute_t @@ -54,27 +56,28 @@ struct lv2_lwmutex_t const u32 protocol; const u64 name; - // this object is not truly a mutex and its syscall names are wrong, it's probabably sleep queue or something - std::atomic signaled; + // this object is not truly a mutex and its syscall names may be wrong, it's probably a sleep queue or something + std::atomic signaled{ 0 }; - // TODO: use sleep queue, possibly remove condition variable - std::condition_variable cv; - std::atomic waiters; + sleep_queue_t sq; lv2_lwmutex_t(u32 protocol, u64 name) : protocol(protocol) , name(name) - , signaled(0) - , waiters(0) { } + + void unlock(lv2_lock_t& lv2_lock); }; REG_ID_TYPE(lv2_lwmutex_t, 0x95); // SYS_LWMUTEX_OBJECT +// Aux +class PPUThread; + // SysCalls s32 _sys_lwmutex_create(vm::ptr lwmutex_id, u32 protocol, vm::ptr control, u32 arg4, u64 name, u32 arg6); s32 _sys_lwmutex_destroy(u32 lwmutex_id); -s32 _sys_lwmutex_lock(u32 lwmutex_id, u64 timeout); +s32 _sys_lwmutex_lock(PPUThread& ppu, u32 lwmutex_id, u64 timeout); s32 _sys_lwmutex_trylock(u32 lwmutex_id); s32 _sys_lwmutex_unlock(u32 lwmutex_id);