mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-31 12:35:51 +00:00
Merge pull request #8725 from orezraey/fix/libkernel-sync-on-address-measured-ps5
fix(libkernel): follow sceKernelSyncOnAddressWait and Wake as measured on a PS5
This commit is contained in:
5 files changed
+208
-130
No files matched your search
@@ -131,25 +131,55 @@ bool WaitOnKeyUntil(std::uintptr_t key, TReady ready, std::optional<std::uint64_
|
||||
}
|
||||
|
||||
template <class TValue>
|
||||
bool WaitOnAddressUntil(TValue* address, TValue expected, std::optional<std::uint64_t> deadlineNanos, const void* caller) {
|
||||
return WaitOnKeyUntil(reinterpret_cast<std::uintptr_t>(address), [&] { return std::atomic_ref<TValue>(*address).load() != expected; }, deadlineNanos, caller);
|
||||
TValue LoadAddressValue(TValue* address) {
|
||||
if (reinterpret_cast<std::uintptr_t>(address) % alignof(TValue) == 0) return std::atomic_ref<TValue>(*address).load();
|
||||
TValue value;
|
||||
asm volatile("mov %1, %0" : "=r"(value) : "m"(*reinterpret_cast<const unsigned char (*)[sizeof(TValue)]>(address)));
|
||||
return value;
|
||||
}
|
||||
|
||||
template <class TValue>
|
||||
int WaitOnAddress(TValue* address, TValue expected, const KernelUseconds* timeout, const void* caller) {
|
||||
const auto deadline = timeout == nullptr ? std::nullopt : std::optional<std::uint64_t>(TimedWait::DeadlineNanos(*timeout));
|
||||
return WaitOnAddressUntil(address, expected, deadline, caller) ? SYNC_ON_ADDRESS_OK : SCE_KERNEL_ERROR_ETIMEDOUT;
|
||||
bool WaitOnAddressUntil(TValue* address, TValue expected, std::optional<std::uint64_t> deadlineNanos, const void* caller, bool& differed) {
|
||||
return WaitOnKeyUntil(reinterpret_cast<std::uintptr_t>(address), [&] { return differed = LoadAddressValue(address) != expected; }, deadlineNanos, caller);
|
||||
}
|
||||
|
||||
void WakeAddressLocked(std::uintptr_t key, std::int32_t count, bool wakesAtLeastOne) {
|
||||
template <class TValue>
|
||||
bool WaitOnAddressUntil(TValue* address, TValue expected, std::optional<std::uint64_t> deadlineNanos, const void* caller) {
|
||||
bool differed = false;
|
||||
return WaitOnAddressUntil(address, expected, deadlineNanos, caller, differed);
|
||||
}
|
||||
|
||||
template <class TValue>
|
||||
int WaitOnAddress(TValue* address, TValue expected, KernelUseconds* timeout, const void* caller) {
|
||||
if (timeout != nullptr && *timeout == 0) {
|
||||
return SCE_KERNEL_ERROR_ETIMEDOUT;
|
||||
}
|
||||
if (address == nullptr) {
|
||||
return SCE_KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
const auto deadline = timeout == nullptr ? std::nullopt : std::optional<std::uint64_t>(TimedWait::DeadlineNanos(*timeout));
|
||||
bool differed = false;
|
||||
const bool woken = WaitOnAddressUntil(address, expected, deadline, caller, differed);
|
||||
if (differed) {
|
||||
return SCE_KERNEL_ERROR_EAGAIN;
|
||||
}
|
||||
if (timeout != nullptr) {
|
||||
const std::uint64_t now = TimedWait::NowNanos();
|
||||
*timeout = woken && *deadline > now ? static_cast<KernelUseconds>((*deadline - now) / 1000) : 0;
|
||||
}
|
||||
return woken ? SYNC_ON_ADDRESS_OK : SCE_KERNEL_ERROR_ETIMEDOUT;
|
||||
}
|
||||
|
||||
std::int32_t WakeAddressLocked(std::uintptr_t key, std::int32_t count, bool wakesAtLeastOne) {
|
||||
auto& bucket = BucketOf(key);
|
||||
std::lock_guard<std::mutex> lock(bucket.mutex);
|
||||
const auto entry = bucket.waiters.find(key);
|
||||
if (entry == bucket.waiters.end()) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
auto& queue = entry->second;
|
||||
for (std::int32_t woken = 0; !queue.empty() && (woken < count || (wakesAtLeastOne && woken == 0)); ++woken) {
|
||||
std::int32_t woken = 0;
|
||||
for (; !queue.empty() && (woken < count || (wakesAtLeastOne && woken == 0)); ++woken) {
|
||||
AddressWaiter* waiter = queue.front();
|
||||
queue.pop_front();
|
||||
waiter->woken = true;
|
||||
@@ -158,10 +188,13 @@ void WakeAddressLocked(std::uintptr_t key, std::int32_t count, bool wakesAtLeast
|
||||
if (queue.empty()) {
|
||||
bucket.waiters.erase(entry);
|
||||
}
|
||||
return woken;
|
||||
}
|
||||
|
||||
void WakeAddress(std::uintptr_t key, std::int32_t count, bool wakesAtLeastOne) {
|
||||
DeferringDelivery([&] { WakeAddressLocked(key, count, wakesAtLeastOne); });
|
||||
std::int32_t WakeAddress(std::uintptr_t key, std::int32_t count, bool wakesAtLeastOne) {
|
||||
std::int32_t woken = 0;
|
||||
DeferringDelivery([&] { woken = WakeAddressLocked(key, count, wakesAtLeastOne); });
|
||||
return woken;
|
||||
}
|
||||
|
||||
std::uint64_t SaturatingNanos(std::int64_t seconds, std::int64_t nanos) {
|
||||
@@ -306,54 +339,33 @@ int SemWake(UserSemaphore* semaphore) {
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) {
|
||||
int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, KernelUseconds* timeout, const char* name) {
|
||||
(void)name;
|
||||
if (!IsAlignedAddress<std::uint32_t>(reinterpret_cast<std::uintptr_t>(address))) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
}
|
||||
return WaitOnAddress(address, expected, timeout, __builtin_return_address(0));
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelSyncOnAddressWait8(std::uint8_t* address, std::uint8_t expected, const KernelUseconds* timeout, const char* name) {
|
||||
int APS5_VABI sceKernelSyncOnAddressWait8(std::uint8_t* address, std::uint8_t expected, KernelUseconds* timeout, const char* name) {
|
||||
(void)name;
|
||||
if (!IsAlignedAddress<std::uint8_t>(reinterpret_cast<std::uintptr_t>(address))) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
}
|
||||
return WaitOnAddress(address, expected, timeout, __builtin_return_address(0));
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelSyncOnAddressWait16(std::uint16_t* address, std::uint16_t expected, const KernelUseconds* timeout, const char* name) {
|
||||
int APS5_VABI sceKernelSyncOnAddressWait16(std::uint16_t* address, std::uint16_t expected, KernelUseconds* timeout, const char* name) {
|
||||
(void)name;
|
||||
if (!IsAlignedAddress<std::uint16_t>(reinterpret_cast<std::uintptr_t>(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) {
|
||||
int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, KernelUseconds* timeout, const char* name) {
|
||||
(void)name;
|
||||
if (!IsAlignedAddress<std::uint32_t>(reinterpret_cast<std::uintptr_t>(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) {
|
||||
int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, KernelUseconds* timeout, const char* name) {
|
||||
(void)name;
|
||||
if (!IsAlignedAddress<std::uint64_t>(reinterpret_cast<std::uintptr_t>(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<std::uintptr_t>(address);
|
||||
if (!IsAlignedAddress<std::uint8_t>(key) || count < 0) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
}
|
||||
|
||||
WakeAddress(key, count, false);
|
||||
return SYNC_ON_ADDRESS_OK;
|
||||
return WakeAddress(reinterpret_cast<std::uintptr_t>(address), count, true);
|
||||
}
|
||||
|
||||
int APS5_VABI _umtx_op_nid_postfix(void* object, int operation, std::uint64_t value, void* sizeArgument, void* timeArgument) {
|
||||
|
||||
@@ -626,11 +626,11 @@ std::int64_t APS5_VABI _sendto_nid_postfix(int descriptor, const void* buffer, s
|
||||
std::int64_t APS5_VABI _recvfrom_nid_postfix(int descriptor, void* buffer, std::uint64_t length, int flags, void* address, std::uint32_t* addressLength);
|
||||
std::int64_t APS5_VABI _sendmsg_nid_postfix(int descriptor, const GuestMsghdr* message, int flags);
|
||||
std::int64_t APS5_VABI _recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, int flags);
|
||||
int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name);
|
||||
int APS5_VABI sceKernelSyncOnAddressWait8(std::uint8_t* address, std::uint8_t expected, const KernelUseconds* timeout, const char* name);
|
||||
int APS5_VABI sceKernelSyncOnAddressWait16(std::uint16_t* address, std::uint16_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 sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, KernelUseconds* timeout, const char* name);
|
||||
int APS5_VABI sceKernelSyncOnAddressWait8(std::uint8_t* address, std::uint8_t expected, KernelUseconds* timeout, const char* name);
|
||||
int APS5_VABI sceKernelSyncOnAddressWait16(std::uint16_t* address, std::uint16_t expected, KernelUseconds* timeout, const char* name);
|
||||
int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, KernelUseconds* timeout, const char* name);
|
||||
int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, KernelUseconds* timeout, const char* name);
|
||||
int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count);
|
||||
int APS5_VABI _umtx_op_nid_postfix(void* object, int operation, std::uint64_t value, void* sizeArgument, void* timeArgument);
|
||||
int APS5_VABI sceCoredumpRegisterCoredumpHandler(uint64_t handler, size_t stackSize, uint64_t context);
|
||||
|
||||
@@ -402,7 +402,7 @@ static void* APS5_VABI AddressWaking(void* arg) {
|
||||
auto& worker = *static_cast<Worker*>(arg);
|
||||
worker.id = std::this_thread::get_id();
|
||||
worker.started.store(true);
|
||||
while (!worker.stop.load()) Require(sceKernelSyncOnAddressWake(&addressWord, 1) == 0);
|
||||
while (!worker.stop.load()) Require(sceKernelSyncOnAddressWake(&addressWord, 1) >= 0);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,23 +6,17 @@
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
static constexpr int SCE_OK = 0;
|
||||
static constexpr KernelUseconds FAILSAFE_TIMEOUT = 10000000;
|
||||
static constexpr KernelUseconds SHORT_TIMEOUT = 1000;
|
||||
static constexpr auto SETTLE = std::chrono::milliseconds(100);
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
|
||||
template <class TCall>
|
||||
static bool Rejects(TCall call) {
|
||||
try { call(); }
|
||||
catch (const std::invalid_argument&) { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
static void AwaitCount(const std::atomic<int>& counter, int value) {
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
|
||||
while (counter.load() != value) {
|
||||
@@ -31,81 +25,93 @@ static void AwaitCount(const std::atomic<int>& counter, int value) {
|
||||
}
|
||||
}
|
||||
|
||||
static void ReturnsWhenTheValueDiffers() {
|
||||
static void FailsAgainWhenTheValueDiffers() {
|
||||
std::uint32_t word = 2;
|
||||
Require(sceKernelSyncOnAddressWait(&word, 1, nullptr, "differs") == SCE_OK);
|
||||
const KernelUseconds timeout = 0;
|
||||
Require(sceKernelSyncOnAddressWait(&word, 1, &timeout, nullptr) == SCE_OK);
|
||||
Require(sceKernelSyncOnAddressWait(&word, 1, nullptr, "differs") == SCE_KERNEL_ERROR_EAGAIN);
|
||||
KernelUseconds timeout = SHORT_TIMEOUT;
|
||||
Require(sceKernelSyncOnAddressWait(&word, 1, &timeout, nullptr) == SCE_KERNEL_ERROR_EAGAIN && timeout == SHORT_TIMEOUT);
|
||||
timeout = 0;
|
||||
Require(sceKernelSyncOnAddressWait(&word, 1, &timeout, nullptr) == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
}
|
||||
|
||||
static void TimesOutWhileTheValueMatches() {
|
||||
std::uint32_t word = 1;
|
||||
const KernelUseconds none = 0;
|
||||
Require(sceKernelSyncOnAddressWait(&word, 1, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
KernelUseconds none = 0;
|
||||
Require(sceKernelSyncOnAddressWait(&word, 1, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT && none == 0);
|
||||
|
||||
const KernelUseconds timeout = 20000;
|
||||
KernelUseconds timeout = 20000;
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
Require(sceKernelSyncOnAddressWait(&word, 1, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15));
|
||||
Require(sceKernelSyncOnAddressWake(&word, INT_MAX) == SCE_OK);
|
||||
Require(timeout == 0);
|
||||
Require(sceKernelSyncOnAddressWake(&word, INT_MAX) == 0);
|
||||
}
|
||||
|
||||
static void WakeReleasesAWaiterWithoutAValueChange() {
|
||||
static void WakeReleasesAWaiterAndReportsTheTimeLeft() {
|
||||
std::uint32_t word = 1;
|
||||
std::atomic<int> started{0};
|
||||
std::atomic<int> finished{0};
|
||||
int result = -1;
|
||||
KernelUseconds timeout = FAILSAFE_TIMEOUT;
|
||||
std::thread waiter([&] {
|
||||
result = sceKernelSyncOnAddressWait(&word, 1, &FAILSAFE_TIMEOUT, "wake");
|
||||
++started;
|
||||
result = sceKernelSyncOnAddressWait(&word, 1, &timeout, "wake");
|
||||
++finished;
|
||||
});
|
||||
AwaitCount(started, 1);
|
||||
std::this_thread::sleep_for(SETTLE);
|
||||
int woken = 0;
|
||||
while (finished.load() == 0) {
|
||||
Require(sceKernelSyncOnAddressWake(&word, 1) == SCE_OK);
|
||||
woken += sceKernelSyncOnAddressWake(&word, 1);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
waiter.join();
|
||||
Require(result == SCE_OK);
|
||||
Require(result == SCE_OK && woken == 1);
|
||||
Require(timeout > 0 && timeout < FAILSAFE_TIMEOUT);
|
||||
}
|
||||
|
||||
static void WakeHonoursTheCountAndTheAddress() {
|
||||
static void WakeCountsOfOneOrLessWakeOneWaiter() {
|
||||
std::uint32_t word = 1;
|
||||
std::uint32_t other = 1;
|
||||
std::atomic<int> started{0};
|
||||
std::atomic<int> finished{0};
|
||||
std::atomic<int> otherFinished{0};
|
||||
std::vector<int> results(3, -1);
|
||||
std::vector<int> results(5, -1);
|
||||
int otherResult = -1;
|
||||
|
||||
std::vector<std::thread> waiters;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
waiters.emplace_back([&, i] {
|
||||
++started;
|
||||
results[i] = sceKernelSyncOnAddressWait(&word, 1, &FAILSAFE_TIMEOUT, "count");
|
||||
KernelUseconds timeout = FAILSAFE_TIMEOUT;
|
||||
results[i] = sceKernelSyncOnAddressWait(&word, 1, &timeout, "count");
|
||||
++finished;
|
||||
});
|
||||
}
|
||||
std::thread otherWaiter([&] {
|
||||
++started;
|
||||
otherResult = sceKernelSyncOnAddressWait(&other, 1, &FAILSAFE_TIMEOUT, "other");
|
||||
KernelUseconds timeout = FAILSAFE_TIMEOUT;
|
||||
otherResult = sceKernelSyncOnAddressWait(&other, 1, &timeout, "other");
|
||||
++otherFinished;
|
||||
});
|
||||
AwaitCount(started, 4);
|
||||
AwaitCount(started, 6);
|
||||
std::this_thread::sleep_for(SETTLE);
|
||||
|
||||
Require(sceKernelSyncOnAddressWake(&word, 0) == SCE_OK);
|
||||
std::this_thread::sleep_for(SETTLE);
|
||||
Require(finished.load() == 0);
|
||||
int expected = 0;
|
||||
for (const std::int32_t count : {0, -1, 1}) {
|
||||
Require(sceKernelSyncOnAddressWake(&word, count) == 1);
|
||||
AwaitCount(finished, ++expected);
|
||||
std::this_thread::sleep_for(SETTLE);
|
||||
Require(finished.load() == expected);
|
||||
}
|
||||
|
||||
Require(sceKernelSyncOnAddressWake(&word, 1) == SCE_OK);
|
||||
AwaitCount(finished, 1);
|
||||
std::this_thread::sleep_for(SETTLE);
|
||||
Require(finished.load() == 1);
|
||||
|
||||
Require(sceKernelSyncOnAddressWake(&word, INT_MAX) == SCE_OK);
|
||||
AwaitCount(finished, 3);
|
||||
Require(sceKernelSyncOnAddressWake(&word, INT_MAX) == 2);
|
||||
AwaitCount(finished, 5);
|
||||
std::this_thread::sleep_for(SETTLE);
|
||||
Require(otherFinished.load() == 0);
|
||||
Require(sceKernelSyncOnAddressWake(&word, INT_MAX) == 0);
|
||||
|
||||
Require(sceKernelSyncOnAddressWake(&other, 1) == SCE_OK);
|
||||
Require(sceKernelSyncOnAddressWake(&other, 1) == 1);
|
||||
for (auto& waiter : waiters) waiter.join();
|
||||
otherWaiter.join();
|
||||
for (const int result : results) Require(result == SCE_OK);
|
||||
@@ -117,38 +123,42 @@ static void WaitsOnTheLowHalfOfAnOnceState() {
|
||||
auto* word = reinterpret_cast<std::uint32_t*>(&state);
|
||||
std::thread waiter([&] {
|
||||
while (state.load() == 1) {
|
||||
Require(sceKernelSyncOnAddressWait(word, 1, nullptr, "once") == SCE_OK);
|
||||
const int result = sceKernelSyncOnAddressWait(word, 1, nullptr, "once");
|
||||
Require(result == SCE_OK || result == SCE_KERNEL_ERROR_EAGAIN);
|
||||
}
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
state.store(2);
|
||||
Require(sceKernelSyncOnAddressWake(word, INT_MAX) == SCE_OK);
|
||||
Require(sceKernelSyncOnAddressWake(word, INT_MAX) >= 0);
|
||||
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);
|
||||
KernelUseconds timeout = SHORT_TIMEOUT;
|
||||
Require(sceKernelSyncOnAddressWait32(&word, 4, &timeout, "differs") == SCE_KERNEL_ERROR_EAGAIN);
|
||||
KernelUseconds none = 0;
|
||||
Require(sceKernelSyncOnAddressWait32(&word, 4, &none, "differs") == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
Require(sceKernelSyncOnAddressWait32(&word, 5, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
|
||||
const KernelUseconds timeout = 20000;
|
||||
timeout = 20000;
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
Require(sceKernelSyncOnAddressWait32(&word, 5, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
Require(sceKernelSyncOnAddressWait32(&word, 5, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT && timeout == 0);
|
||||
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15));
|
||||
timeout = 20000;
|
||||
start = std::chrono::steady_clock::now();
|
||||
Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT && timeout == 0);
|
||||
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);
|
||||
KernelUseconds timeout = SHORT_TIMEOUT;
|
||||
Require(sceKernelSyncOnAddressWait64(&value, 0x1ULL, &timeout, "high") == SCE_KERNEL_ERROR_EAGAIN);
|
||||
Require(sceKernelSyncOnAddressWait64(&value, 0x200000001ULL, &timeout, "high") == SCE_KERNEL_ERROR_EAGAIN);
|
||||
Require(sceKernelSyncOnAddressWait64(&value, 0x100000001ULL, &timeout, "high") == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
}
|
||||
|
||||
static void WakeReleasesWaitersOfEverySize() {
|
||||
@@ -161,23 +171,26 @@ static void WakeReleasesWaitersOfEverySize() {
|
||||
std::vector<std::thread> waiters;
|
||||
waiters.emplace_back([&] {
|
||||
++started;
|
||||
results[0] = sceKernelSyncOnAddressWait(word, 1, &FAILSAFE_TIMEOUT, "sized");
|
||||
KernelUseconds timeout = FAILSAFE_TIMEOUT;
|
||||
results[0] = sceKernelSyncOnAddressWait(word, 1, &timeout, "sized");
|
||||
++finished;
|
||||
});
|
||||
waiters.emplace_back([&] {
|
||||
++started;
|
||||
results[1] = sceKernelSyncOnAddressWait32(word, 1, &FAILSAFE_TIMEOUT, "sized");
|
||||
KernelUseconds timeout = FAILSAFE_TIMEOUT;
|
||||
results[1] = sceKernelSyncOnAddressWait32(word, 1, &timeout, "sized");
|
||||
++finished;
|
||||
});
|
||||
waiters.emplace_back([&] {
|
||||
++started;
|
||||
results[2] = sceKernelSyncOnAddressWait64(&value, 1, &FAILSAFE_TIMEOUT, "sized");
|
||||
KernelUseconds timeout = FAILSAFE_TIMEOUT;
|
||||
results[2] = sceKernelSyncOnAddressWait64(&value, 1, &timeout, "sized");
|
||||
++finished;
|
||||
});
|
||||
AwaitCount(started, 3);
|
||||
std::this_thread::sleep_for(SETTLE);
|
||||
|
||||
Require(sceKernelSyncOnAddressWake(&value, 1) == SCE_OK);
|
||||
Require(sceKernelSyncOnAddressWake(&value, 1) == 1);
|
||||
AwaitCount(finished, 1);
|
||||
std::this_thread::sleep_for(SETTLE);
|
||||
Require(finished.load() == 1);
|
||||
@@ -185,7 +198,7 @@ static void WakeReleasesWaitersOfEverySize() {
|
||||
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);
|
||||
Require(sceKernelSyncOnAddressWake(&value, INT_MAX) >= 0);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
for (auto& waiter : waiters) waiter.join();
|
||||
@@ -193,14 +206,15 @@ static void WakeReleasesWaitersOfEverySize() {
|
||||
}
|
||||
|
||||
static void NarrowWaitsCompareOnlyTheirWidth() {
|
||||
const KernelUseconds none = 0;
|
||||
KernelUseconds timeout = SHORT_TIMEOUT;
|
||||
alignas(4) std::uint8_t bytes[4] = {1, 5, 7, 7};
|
||||
Require(sceKernelSyncOnAddressWait8(&bytes[1], 4, &none, "differs") == SCE_OK);
|
||||
Require(sceKernelSyncOnAddressWait8(&bytes[1], 5, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
Require(sceKernelSyncOnAddressWait8(&bytes[1], 4, &timeout, "differs") == SCE_KERNEL_ERROR_EAGAIN);
|
||||
Require(sceKernelSyncOnAddressWait8(&bytes[1], 5, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
|
||||
timeout = SHORT_TIMEOUT;
|
||||
alignas(4) std::uint16_t halves[2] = {1, 0x0705};
|
||||
Require(sceKernelSyncOnAddressWait16(&halves[1], 0x0005, &none, "high") == SCE_OK);
|
||||
Require(sceKernelSyncOnAddressWait16(&halves[1], 0x0705, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
Require(sceKernelSyncOnAddressWait16(&halves[1], 0x0005, &timeout, "high") == SCE_KERNEL_ERROR_EAGAIN);
|
||||
Require(sceKernelSyncOnAddressWait16(&halves[1], 0x0705, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
}
|
||||
|
||||
static void WakeReleasesNarrowWaitersAtTheirOwnAddress() {
|
||||
@@ -216,26 +230,28 @@ static void WakeReleasesNarrowWaitersAtTheirOwnAddress() {
|
||||
|
||||
std::thread byteWaiter([&] {
|
||||
++started;
|
||||
byteResult = sceKernelSyncOnAddressWait8(&bytes[1], 3, &FAILSAFE_TIMEOUT, "byte");
|
||||
KernelUseconds timeout = FAILSAFE_TIMEOUT;
|
||||
byteResult = sceKernelSyncOnAddressWait8(&bytes[1], 3, &timeout, "byte");
|
||||
++byteFinished;
|
||||
});
|
||||
std::thread halfWaiter([&] {
|
||||
++started;
|
||||
halfResult = sceKernelSyncOnAddressWait16(half, 9, &FAILSAFE_TIMEOUT, "half");
|
||||
KernelUseconds timeout = FAILSAFE_TIMEOUT;
|
||||
halfResult = sceKernelSyncOnAddressWait16(half, 9, &timeout, "half");
|
||||
++halfFinished;
|
||||
});
|
||||
AwaitCount(started, 2);
|
||||
std::this_thread::sleep_for(SETTLE);
|
||||
|
||||
Require(sceKernelSyncOnAddressWake(&bytes[0], INT_MAX) == SCE_OK);
|
||||
Require(sceKernelSyncOnAddressWake(&bytes[3], INT_MAX) == SCE_OK);
|
||||
Require(sceKernelSyncOnAddressWake(&bytes[0], INT_MAX) == 0);
|
||||
Require(sceKernelSyncOnAddressWake(&bytes[3], INT_MAX) == 0);
|
||||
std::this_thread::sleep_for(SETTLE);
|
||||
Require(byteFinished.load() == 0 && halfFinished.load() == 0);
|
||||
|
||||
Require(sceKernelSyncOnAddressWake(&bytes[1], 1) == SCE_OK);
|
||||
Require(sceKernelSyncOnAddressWake(&bytes[1], 1) == 1);
|
||||
AwaitCount(byteFinished, 1);
|
||||
Require(halfFinished.load() == 0);
|
||||
Require(sceKernelSyncOnAddressWake(half, 1) == SCE_OK);
|
||||
Require(sceKernelSyncOnAddressWake(half, 1) == 1);
|
||||
AwaitCount(halfFinished, 1);
|
||||
|
||||
byteWaiter.join();
|
||||
@@ -243,36 +259,87 @@ static void WakeReleasesNarrowWaitersAtTheirOwnAddress() {
|
||||
Require(byteResult == SCE_OK && halfResult == SCE_OK);
|
||||
}
|
||||
|
||||
static void RejectsInvalidArguments() {
|
||||
std::uint32_t words[2] = {1, 1};
|
||||
auto* misaligned = reinterpret_cast<std::uint32_t*>(reinterpret_cast<unsigned char*>(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(sceKernelSyncOnAddressWake(misaligned, 1) == SCE_OK);
|
||||
Require(Rejects([&] { sceKernelSyncOnAddressWait8(nullptr, 1, nullptr, nullptr); }));
|
||||
Require(Rejects([&] { sceKernelSyncOnAddressWait16(nullptr, 1, nullptr, nullptr); }));
|
||||
Require(Rejects([&] { sceKernelSyncOnAddressWait16(reinterpret_cast<std::uint16_t*>(reinterpret_cast<unsigned char*>(words) + 1), 1, nullptr, nullptr); }));
|
||||
Require(Rejects([&] { sceKernelSyncOnAddressWake(words, -1); }));
|
||||
static void MisalignedAddressesWaitAndWake() {
|
||||
alignas(16) unsigned char storage[16] = {};
|
||||
auto* word = reinterpret_cast<std::uint32_t*>(storage + 1);
|
||||
auto* quad = reinterpret_cast<std::uint64_t*>(storage + 4);
|
||||
const std::uint32_t wordValue = 0x11223344;
|
||||
std::memcpy(word, &wordValue, sizeof(wordValue));
|
||||
KernelUseconds timeout = SHORT_TIMEOUT;
|
||||
Require(sceKernelSyncOnAddressWait32(word, 1, &timeout, "misaligned") == SCE_KERNEL_ERROR_EAGAIN);
|
||||
Require(sceKernelSyncOnAddressWait32(word, wordValue, &timeout, "misaligned") == SCE_KERNEL_ERROR_ETIMEDOUT && timeout == 0);
|
||||
timeout = SHORT_TIMEOUT;
|
||||
Require(sceKernelSyncOnAddressWait64(quad, 1, &timeout, "misaligned") == SCE_KERNEL_ERROR_EAGAIN);
|
||||
timeout = SHORT_TIMEOUT;
|
||||
Require(sceKernelSyncOnAddressWait16(reinterpret_cast<std::uint16_t*>(storage + 9), 1, &timeout, "misaligned") == SCE_KERNEL_ERROR_EAGAIN);
|
||||
|
||||
std::uint64_t values[2] = {1, 1};
|
||||
auto* wordAligned = reinterpret_cast<std::uint64_t*>(reinterpret_cast<unsigned char*>(values) + sizeof(std::uint32_t));
|
||||
Require(Rejects([&] { sceKernelSyncOnAddressWait64(nullptr, 1, nullptr, nullptr); }));
|
||||
Require(Rejects([&] { sceKernelSyncOnAddressWait64(wordAligned, 1, nullptr, nullptr); }));
|
||||
std::atomic<int> started{0};
|
||||
int result = -1;
|
||||
std::thread waiter([&] {
|
||||
++started;
|
||||
KernelUseconds waitTimeout = FAILSAFE_TIMEOUT;
|
||||
result = sceKernelSyncOnAddressWait32(word, wordValue, &waitTimeout, "misaligned");
|
||||
});
|
||||
AwaitCount(started, 1);
|
||||
std::this_thread::sleep_for(SETTLE);
|
||||
Require(sceKernelSyncOnAddressWake(storage, INT_MAX) == 0);
|
||||
Require(sceKernelSyncOnAddressWake(word, 1) == 1);
|
||||
waiter.join();
|
||||
Require(result == SCE_OK);
|
||||
|
||||
auto* shared = reinterpret_cast<std::uint64_t*>(storage + 3);
|
||||
std::atomic<bool> stop{false};
|
||||
std::atomic<int> rounds{0};
|
||||
std::thread changer([&] {
|
||||
std::uint64_t next = 1;
|
||||
while (!stop.load()) {
|
||||
__atomic_store_n(reinterpret_cast<unsigned char*>(shared), static_cast<unsigned char>(next), __ATOMIC_RELAXED);
|
||||
++next;
|
||||
sceKernelSyncOnAddressWake(shared, INT_MAX);
|
||||
}
|
||||
});
|
||||
for (int round = 0; round < 2000; ++round) {
|
||||
unsigned char bytes[sizeof(std::uint64_t)];
|
||||
bytes[0] = __atomic_load_n(reinterpret_cast<unsigned char*>(shared), __ATOMIC_RELAXED);
|
||||
std::memcpy(bytes + 1, reinterpret_cast<unsigned char*>(shared) + 1, sizeof(bytes) - 1);
|
||||
std::uint64_t seen = 0;
|
||||
std::memcpy(&seen, bytes, sizeof(seen));
|
||||
KernelUseconds waitTimeout = SHORT_TIMEOUT;
|
||||
const int waited = sceKernelSyncOnAddressWait64(shared, seen, &waitTimeout, "changing");
|
||||
Require(waited == SCE_OK || waited == SCE_KERNEL_ERROR_EAGAIN || waited == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
++rounds;
|
||||
}
|
||||
stop.store(true);
|
||||
changer.join();
|
||||
Require(rounds.load() == 2000);
|
||||
}
|
||||
|
||||
static void NullAddresses() {
|
||||
KernelUseconds timeout = SHORT_TIMEOUT;
|
||||
Require(sceKernelSyncOnAddressWait(nullptr, 1, &timeout, nullptr) == SCE_KERNEL_ERROR_EFAULT && timeout == SHORT_TIMEOUT);
|
||||
Require(sceKernelSyncOnAddressWait8(nullptr, 1, nullptr, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
Require(sceKernelSyncOnAddressWait16(nullptr, 1, nullptr, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
Require(sceKernelSyncOnAddressWait32(nullptr, 1, nullptr, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
Require(sceKernelSyncOnAddressWait64(nullptr, 1, nullptr, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
||||
KernelUseconds none = 0;
|
||||
Require(sceKernelSyncOnAddressWait32(nullptr, 1, &none, nullptr) == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
Require(sceKernelSyncOnAddressWake(nullptr, 1) == 0);
|
||||
Require(sceKernelSyncOnAddressWake(nullptr, 0) == 0);
|
||||
std::uint32_t word = 0;
|
||||
Require(sceKernelSyncOnAddressWake(&word, -1) == 0);
|
||||
}
|
||||
|
||||
int main() {
|
||||
ReturnsWhenTheValueDiffers();
|
||||
FailsAgainWhenTheValueDiffers();
|
||||
TimesOutWhileTheValueMatches();
|
||||
WakeReleasesAWaiterWithoutAValueChange();
|
||||
WakeHonoursTheCountAndTheAddress();
|
||||
WakeReleasesAWaiterAndReportsTheTimeLeft();
|
||||
WakeCountsOfOneOrLessWakeOneWaiter();
|
||||
WaitsOnTheLowHalfOfAnOnceState();
|
||||
SizedWaitsTimeOutWhileTheValueMatches();
|
||||
Wait64ComparesTheHighHalf();
|
||||
WakeReleasesWaitersOfEverySize();
|
||||
NarrowWaitsCompareOnlyTheirWidth();
|
||||
WakeReleasesNarrowWaitersAtTheirOwnAddress();
|
||||
RejectsInvalidArguments();
|
||||
MisalignedAddressesWaitAndWake();
|
||||
NullAddresses();
|
||||
}
|
||||
@@ -70,8 +70,7 @@
|
||||
- [Foreign guest module image protection on Windows](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - `sceKernelMprotect` on Windows allows protection mutations on mapped PE images containing a `.elf0` section (as relinked guest modules produce, such as NVIDIA Real-time Denoisers `libNRD.prx` bundled in Dying Light 2: Stay Human PPSA02262), registering their sections into `GuestAllocations`. Host libraries and system DLLs (such as `libkernel.prx` and `kernel32.dll`) without a `.elf0` section continue to be rejected
|
||||
- [_umtx_op](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the mutex, condition variable, read-write lock, legacy lock and `SEM2` operations throw, and so does a misaligned object. Implemented as FreeBSD 11's [kern_umtx.c](https://github.com/freebsd/freebsd-src/blob/stable/11/sys/kern/kern_umtx.c): `UMTX_OP_WAIT`, `UMTX_OP_WAKE`, `UMTX_OP_WAIT_UINT`, `UMTX_OP_WAIT_UINT_PRIVATE`, `UMTX_OP_WAKE_PRIVATE`, `UMTX_OP_NWAKE_PRIVATE`, `UMTX_OP_SEM_WAIT` and `UMTX_OP_SEM_WAKE` (the last two built under `COMPAT_FREEBSD9`/`10`). Operations 0 and 1 fail with `EOPNOTSUPP`, as measured on a PS5 (firmware 12.70). The `SEM2` operations, 23 and 24, were measured on the console and returned `0` for a count of 0 where FreeBSD would time out, so they stay unimplemented.
|
||||
- [sceKernelCreateSema, sceKernelPollSema, sceKernelSignalSema, sceKernelDeleteSema](../../../core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp) (libkernel) - a poll, a signal or a second delete of a deleted semaphore reads freed memory here, where the console returns `0x80020003` (ESRCH) ([measured](https://github.com/mattias800/prosper/blob/3ea5cb9a846b6dcd00f5073360462bbda4f72288/prosper/tests/data/console_oracle/kernel.golden.tsv#L17-L19)). A null output pointer to `sceKernelCreateSema` (a fault on PS5 firmware 13.60) and attributes 0x100 to 0x102 (accepted there, effect unknown) throw. Other attributes above 2 and negative maximum and signal counts return `EINVAL` here, as there. `EINVAL` for an initial count above the maximum or negative, a maximum of 0, a null name, a poll of 0 or above the maximum and a signal of 0 is [measured](https://github.com/mattias800/prosper/blob/3ea5cb9a846b6dcd00f5073360462bbda4f72288/prosper/tests/data/console_oracle/kernel.golden.tsv#L10-L23).
|
||||
- [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
|
||||
- [sceKernelSyncOnAddressWait8 / sceKernelSyncOnAddressWait16 / sceKernelSyncOnAddressWait32 / sceKernelSyncOnAddressWait64](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - libc's `__std_atomic_wait_direct_8/16/32/64` pass a microsecond timeout or null and ignore the result; the by-value comparand, return values and the 2- and 8-byte alignment of Wait16 and Wait64 are assumed
|
||||
- [sceKernelSyncOnAddressWait, sceKernelSyncOnAddressWait8/16/32/64 and sceKernelSyncOnAddressWake](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - follow a payload process on a PS5 (firmware 13.60), not a title. Only a null address is checked for `EFAULT`: any other unreadable address is read and faults the host, where the console returned `EFAULT` for address 0x10. The time left that a woken wait writes back is taken from the host clock, so it can differ from the console's by the cost of the call
|
||||
- [scePthreadRwlockDestroy, pthread_rwlock_destroy](../../../core/libs/prx/libkernel/Pthread/src/Rwlock.cpp) (libkernel) - measured on a PS5 (firmware 13.60): destroying a rwlock returns 0 whether it is free or held for reading or writing, and leaves 1 in the handle; every call on that handle then returns `EINVAL` until it is initialized again, and a zero handle is initialized on first use by the `scePthreadRwlock` calls as by the POSIX ones. A rwlock destroyed while held is not freed, so its memory leaks. Destroying a rwlock while another thread is acquiring it is not guarded
|
||||
- [scePthreadCondWait / scePthreadCondTimedwait](../../../core/libs/prx/libkernel/Pthread/src/Cond.cpp) (libkernel) - the mutex's owner is checked before waiting, as in FreeBSD libthr's [`cond_wait_common`](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_cond.c#L1992-L2029). Measured in a payload process on a PS5 (firmware 13.60): `EPERM` (`0x80020001`) for an unowned mutex of every type, the adaptive initializer (slot 1), an uninitialized slot and a mutex owned by another thread, `EINVAL` for the destroyed mutex (slot 2), and a recursive mutex is released completely and relocked to its depth. `scePthreadCondDestroy` stores 1, which every condition call refuses with `EINVAL`, and leaves an uninitialized slot alone. Cancellation restores the mutex and depth before cleanup and a canceled waiter forwards a wake, per POSIX; its ordering is not measured
|
||||
- [sceKernelOpenEventFlag](../../../core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp) (libkernel) - opens only flags this process created with attribute 0x100. On the console, other processes' 0x100 flags, such as the shell's `SceShellCoreUtilAppFocus`, can be opened too. They do not exist here, so opening them returns `SCE_KERNEL_ERROR_ESRCH`. A null output with the name of an openable flag returns `SCE_KERNEL_ERROR_EFAULT`, the host's answer to the console's fault there, not a measured code. With a plain flag's name it returns `SCE_KERNEL_ERROR_ESRCH`, as measured; with a missing name, or a null output and a missing name, it returns `SCE_KERNEL_ERROR_ESRCH`, as a payload process on PS5 firmware 13.60 got.
|
||||
|
||||
Reference in new issue
Block a user