diff --git a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp index 87944ae04..733ae7c41 100644 --- a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp +++ b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp @@ -25,23 +25,16 @@ struct AddressWaiter { std::mutex g_waitersLock; std::unordered_map> g_waiters; -bool IsWordAddress(std::uintptr_t address) { - return address != 0 && address % alignof(std::uint32_t) == 0; +template +bool IsAlignedAddress(std::uintptr_t address) { + return address != 0 && address % alignof(TValue) == 0; } -} // namespace - -extern "C" { - -int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { - (void)name; +template +int WaitOnAddress(TValue* address, TValue expected, const KernelUseconds* timeout, const void* caller) { const auto key = reinterpret_cast(address); - if (!IsWordAddress(key)) { - APS5_INVALID_ARG_EX; - } - std::unique_lock lock(g_waitersLock); - if (std::atomic_ref(*address).load() != expected) { + if (std::atomic_ref(*address).load() != expected) { return SYNC_ON_ADDRESS_OK; } @@ -65,13 +58,41 @@ int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t e } } lock.unlock(); - KernelTraceWait_nid_postfix("addr", __builtin_return_address(0), static_cast(waited.count()), !woken); + KernelTraceWait_nid_postfix("addr", caller, static_cast(waited.count()), !woken); return woken ? SYNC_ON_ADDRESS_OK : SCE_KERNEL_ERROR_ETIMEDOUT; } +} // namespace + +extern "C" { + +int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + +int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + +int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count) { const auto key = reinterpret_cast(address); - if (!IsWordAddress(key) || count < 0) { + if (!IsAlignedAddress(key) || count < 0) { APS5_INVALID_ARG_EX; } diff --git a/core/libs/tests/GuestSyncOnAddress.cpp b/core/libs/tests/GuestSyncOnAddress.cpp index 363c35d9a..e5954051f 100644 --- a/core/libs/tests/GuestSyncOnAddress.cpp +++ b/core/libs/tests/GuestSyncOnAddress.cpp @@ -10,6 +10,8 @@ extern "C" { int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, const KernelUseconds* timeout, const char* name); int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count); } @@ -130,14 +132,87 @@ static void WaitsOnTheLowHalfOfAnOnceState() { waiter.join(); } +static void SizedWaitsTimeOutWhileTheValueMatches() { + std::uint32_t word = 5; + std::uint64_t value = 0x500000005ULL; + const KernelUseconds none = 0; + Require(sceKernelSyncOnAddressWait32(&word, 4, &none, "differs") == SCE_OK); + Require(sceKernelSyncOnAddressWait32(&word, 5, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + + const KernelUseconds timeout = 20000; + auto start = std::chrono::steady_clock::now(); + Require(sceKernelSyncOnAddressWait32(&word, 5, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15)); + start = std::chrono::steady_clock::now(); + Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15)); +} + +static void Wait64ComparesTheHighHalf() { + std::uint64_t value = 0x100000001ULL; + const KernelUseconds none = 0; + Require(sceKernelSyncOnAddressWait64(&value, 0x1ULL, &none, "high") == SCE_OK); + Require(sceKernelSyncOnAddressWait64(&value, 0x200000001ULL, &none, "high") == SCE_OK); + Require(sceKernelSyncOnAddressWait64(&value, 0x100000001ULL, &none, "high") == SCE_KERNEL_ERROR_ETIMEDOUT); +} + +static void WakeReleasesWaitersOfEverySize() { + std::uint64_t value = 1; + auto* word = reinterpret_cast(&value); + std::atomic started{0}; + std::atomic finished{0}; + std::vector results(3, -1); + + std::vector waiters; + waiters.emplace_back([&] { + ++started; + results[0] = sceKernelSyncOnAddressWait(word, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + waiters.emplace_back([&] { + ++started; + results[1] = sceKernelSyncOnAddressWait32(word, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + waiters.emplace_back([&] { + ++started; + results[2] = sceKernelSyncOnAddressWait64(&value, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + AwaitCount(started, 3); + std::this_thread::sleep_for(SETTLE); + + Require(sceKernelSyncOnAddressWake(&value, 1) == SCE_OK); + AwaitCount(finished, 1); + std::this_thread::sleep_for(SETTLE); + Require(finished.load() == 1); + + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (finished.load() != 3) { + Require(std::chrono::steady_clock::now() < deadline); + Require(sceKernelSyncOnAddressWake(&value, INT_MAX) == SCE_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + for (auto& waiter : waiters) waiter.join(); + for (const int result : results) Require(result == SCE_OK); +} + static void RejectsInvalidArguments() { std::uint32_t words[2] = {1, 1}; auto* misaligned = reinterpret_cast(reinterpret_cast(words) + 1); Require(Rejects([&] { sceKernelSyncOnAddressWait(nullptr, 1, nullptr, nullptr); })); Require(Rejects([&] { sceKernelSyncOnAddressWait(misaligned, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait32(nullptr, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait32(misaligned, 1, nullptr, nullptr); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(nullptr, 1); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(misaligned, 1); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(words, -1); })); + + std::uint64_t values[2] = {1, 1}; + auto* wordAligned = reinterpret_cast(reinterpret_cast(values) + sizeof(std::uint32_t)); + Require(Rejects([&] { sceKernelSyncOnAddressWait64(nullptr, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait64(wordAligned, 1, nullptr, nullptr); })); } int main() { @@ -146,5 +221,8 @@ int main() { WakeReleasesAWaiterWithoutAValueChange(); WakeHonoursTheCountAndTheAddress(); WaitsOnTheLowHalfOfAnOnceState(); + SizedWaitsTimeOutWhileTheValueMatches(); + Wait64ComparesTheHighHalf(); + WakeReleasesWaitersOfEverySize(); RejectsInvalidArguments(); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 92cad4624..245e49285 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -44,6 +44,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAgcDcbPrimeUtcl2](../../core/libs/prx/libSceAgc/DcbState/src/Display.cpp) (libSceAgc) - assumed to write the packet `sceAgcAcbPrimeUtcl2` writes: PRIME_UTCL2 has the same 5 dwords on the graphics and compute rings. Not confirmed on a title. Both write a NOP of that size, as there is no TLB to prime - [sceKernelInternalMemoryGetModuleSegmentInfo](../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature - [sceKernelSyncOnAddressWait](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the only known caller passes a null timeout and a name string as the fourth argument; the timeout is assumed to point to microseconds like the other kernel waits, and the name is ignored +- [sceKernelSyncOnAddressWait32 / sceKernelSyncOnAddressWait64](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - libc's `__std_atomic_wait_direct_32/64` pass a microsecond timeout or null and ignore the result; the by-value comparand, return values and Wait64's 8-byte alignment are assumed - [sceKernelMapNamedFlexibleMemoryInternal](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - flag 0x8000 unknown; only the sceKernelMapNamedFlexibleMemory flags are accepted - [sceLibcInternalBacktraceForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature - [sceLibcInternalHeapErrorReportForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature