mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-21 16:12:47 -05:00
Merge pull request #5887 from leorossi2005/feat/libkernel-umtx-microsecond-timeout
feat(libkernel): support microsecond timeout in size-0 _umtx_op
This commit is contained in:
3 files changed
+23
-20
No files matched your search
@@ -158,6 +158,11 @@ int UmtxDeadline(const void* sizeArgument, const void* timeArgument, std::option
|
||||
if (timeArgument == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
if (sizeArgument == nullptr) {
|
||||
const auto usecs = *static_cast<const std::uint32_t*>(timeArgument);
|
||||
deadlineNanos = TimedWait::DeadlineNanos(usecs);
|
||||
return 0;
|
||||
}
|
||||
UmtxTime time{{0, 0}, 0, GUEST_CLOCK_REALTIME};
|
||||
if (reinterpret_cast<std::uintptr_t>(sizeArgument) <= sizeof(KernelTimespec)) {
|
||||
time.timeout = *static_cast<const KernelTimespec*>(timeArgument);
|
||||
@@ -165,9 +170,6 @@ int UmtxDeadline(const void* sizeArgument, const void* timeArgument, std::option
|
||||
time = *static_cast<const UmtxTime*>(timeArgument);
|
||||
}
|
||||
if (time.timeout.tv_sec < 0 || time.timeout.tv_nsec < 0 || time.timeout.tv_nsec >= NANOS_PER_SECOND) {
|
||||
if (sizeArgument == nullptr) {
|
||||
NotImplemented_nid_no_patch("_umtx_op timeout with size argument 0 that is not a valid timespec");
|
||||
}
|
||||
return GUEST_EINVAL;
|
||||
}
|
||||
|
||||
|
||||
@@ -123,19 +123,20 @@ static void TimesOutWhileTheValueMatches() {
|
||||
static void RejectsAnInvalidTimeout() {
|
||||
std::uint32_t word = 1;
|
||||
KernelTimespec negative{-1, 0};
|
||||
Require(FailsWith(Umtx(&word, UMTX_OP_WAIT_UINT, 1, SizeOf(sizeof(negative)), &negative), GUEST_EINVAL));
|
||||
KernelTimespec tooManyNanos{0, 1000000000};
|
||||
Require(FailsWith(Umtx(&word, UMTX_OP_WAIT_UINT, 1, SizeOf(sizeof(tooManyNanos)), &tooManyNanos), GUEST_EINVAL));
|
||||
for (const int operation : {UMTX_OP_WAIT, UMTX_OP_WAIT_UINT}) {
|
||||
bool threw = false;
|
||||
try { Umtx(&word, operation, 1, nullptr, &tooManyNanos); }
|
||||
catch (const std::runtime_error&) { threw = true; }
|
||||
Require(threw);
|
||||
threw = false;
|
||||
try { Umtx(&word, operation, 1, nullptr, &negative); }
|
||||
catch (const std::runtime_error&) { threw = true; }
|
||||
Require(threw);
|
||||
Require(FailsWith(Umtx(&word, operation, 1, SizeOf(sizeof(negative)), &negative), GUEST_EINVAL));
|
||||
Require(FailsWith(Umtx(&word, operation, 1, SizeOf(sizeof(tooManyNanos)), &tooManyNanos), GUEST_EINVAL));
|
||||
}
|
||||
struct {
|
||||
std::uint32_t usecs = 20000;
|
||||
std::uint32_t zero32 = 0;
|
||||
std::int64_t zero64 = 0;
|
||||
} zeroNeighbours;
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
Require(FailsWith(Umtx(&word, UMTX_OP_WAIT_UINT, 1, nullptr, &zeroNeighbours), GUEST_ETIMEDOUT));
|
||||
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15));
|
||||
Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(1));
|
||||
UmtxTime negativeNanos{{0, -1}, 0, CLOCK_MONOTONIC_ID};
|
||||
Require(FailsWith(Umtx(&word, UMTX_OP_WAIT_UINT, 1, SizeOf(sizeof(negativeNanos)), &negativeNanos), GUEST_EINVAL));
|
||||
}
|
||||
@@ -146,7 +147,7 @@ static void WakeReleasesAWaiterWithoutAValueChange() {
|
||||
int result = -2;
|
||||
std::thread waiter([&] {
|
||||
KernelTimespec failsafe = FailsafeTimeout();
|
||||
result = Umtx(&value, UMTX_OP_WAIT, 1, nullptr, &failsafe);
|
||||
result = Umtx(&value, UMTX_OP_WAIT, 1, SizeOf(sizeof(failsafe)), &failsafe);
|
||||
++finished;
|
||||
});
|
||||
while (finished.load() == 0) {
|
||||
@@ -171,14 +172,14 @@ static void WakeHonoursTheCountAndTheAddress() {
|
||||
waiters.emplace_back([&, i] {
|
||||
KernelTimespec failsafe = FailsafeTimeout();
|
||||
++started;
|
||||
results[i] = Umtx(&word, UMTX_OP_WAIT_UINT_PRIVATE, 1, nullptr, &failsafe);
|
||||
results[i] = Umtx(&word, UMTX_OP_WAIT_UINT_PRIVATE, 1, SizeOf(sizeof(failsafe)), &failsafe);
|
||||
++finished;
|
||||
});
|
||||
}
|
||||
std::thread otherWaiter([&] {
|
||||
KernelTimespec failsafe = FailsafeTimeout();
|
||||
++started;
|
||||
otherResult = Umtx(&other, UMTX_OP_WAIT_UINT, 1, nullptr, &failsafe);
|
||||
otherResult = Umtx(&other, UMTX_OP_WAIT_UINT, 1, SizeOf(sizeof(failsafe)), &failsafe);
|
||||
++otherFinished;
|
||||
});
|
||||
AwaitCount(started, 5);
|
||||
@@ -227,20 +228,20 @@ static void NwakeWakesEveryWaiterOnEachListedAddress() {
|
||||
waiters.emplace_back([&, i] {
|
||||
KernelTimespec failsafe = FailsafeTimeout();
|
||||
++started;
|
||||
results[i] = Umtx(&first, UMTX_OP_WAIT_UINT_PRIVATE, 1, nullptr, &failsafe);
|
||||
results[i] = Umtx(&first, UMTX_OP_WAIT_UINT_PRIVATE, 1, SizeOf(sizeof(failsafe)), &failsafe);
|
||||
++finished;
|
||||
});
|
||||
}
|
||||
waiters.emplace_back([&] {
|
||||
KernelTimespec failsafe = FailsafeTimeout();
|
||||
++started;
|
||||
results[3] = Umtx(&second, UMTX_OP_WAIT, 1, nullptr, &failsafe);
|
||||
results[3] = Umtx(&second, UMTX_OP_WAIT, 1, SizeOf(sizeof(failsafe)), &failsafe);
|
||||
++finished;
|
||||
});
|
||||
std::thread unlistedWaiter([&] {
|
||||
KernelTimespec failsafe = FailsafeTimeout();
|
||||
++started;
|
||||
unlistedResult = Umtx(&unlisted, UMTX_OP_WAIT_UINT, 1, nullptr, &failsafe);
|
||||
unlistedResult = Umtx(&unlisted, UMTX_OP_WAIT_UINT, 1, SizeOf(sizeof(failsafe)), &failsafe);
|
||||
++unlistedFinished;
|
||||
});
|
||||
AwaitCount(started, 5);
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
- [sceKernelAvailableFlexibleMemorySize](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - the segments of the title's executable and of its `.guest.prx` modules count against the configured flexible-memory budget (448 MiB by default) while loaded, for this size and for flexible maps. A title may declare a larger budget through `kernel.flexibleMemorySize` in `param.json`; this does not reduce the 13.5 GiB direct-memory size. Rejection of zero or a value not aligned to 16 KiB has not been measured on a console. This segment-accounting model follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/module.cpp#L165), which maps every segment of a module outside its system module directory as flexible memory on the PS4. [Counted](../../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) are the segments the title's ELF gives permissions, each rounded to 16 KiB pages; not counted are segments without permissions, the relinker's own blocks (on Linux both are the read/write/execute `PT_LOAD`s, plus the header block at file offset 0; on Windows everything outside the `.elf` sections) and the host system libraries. The Linux image carries no marker for the relinker's blocks, so they are told apart by those flags and that offset only: a title segment that is writable and executable, or starts at file offset 0, is not counted. A module whose segments exceed what is left still loads, and the available size then reads 0; shadPS4 fails the segment map instead ([memory.cpp](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/memory.cpp#L519-L524) returns `EINVAL`, [module.cpp](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/module.cpp#L168-L169) asserts). Which segments the PS5 charges, its rounding, whether system modules use the title's budget and when an unload releases the charge are not measured. PPSA15508 subtracts the reported size and a 10 MiB module allowance from its own 448 MiB budget at start-up and stops when the result exceeds its 146 MiB code and data limit ([#2883](https://github.com/boykopovar/AnyPS5/issues/2883))
|
||||
- [Memory protection bits 0x100 and 0x200](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - taken as audio coprocessor (ACP) read and write, as in the [KytyPS5](https://github.com/KytyPS5/KytyPS5) and [etaHEN](https://github.com/etaHEN/etaHEN) headers; the coprocessor work runs on the host CPU, so they grant host read and read/write access like the GPU bits
|
||||
- [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) - `UMTX_OP_WAIT`, `UMTX_OP_WAKE`, `UMTX_OP_WAIT_UINT`, `UMTX_OP_WAIT_UINT_PRIVATE`, `UMTX_OP_WAKE_PRIVATE` and `UMTX_OP_NWAKE_PRIVATE` follow FreeBSD 11's [kern_umtx.c](https://github.com/freebsd/freebsd-src/blob/stable/11/sys/kern/kern_umtx.c): a timeout whose size argument is at most 16 is a relative `timespec`, a larger one an `_umtx_time`, and a wake of 0 or fewer wakes one waiter. `UMTX_OP_NWAKE_PRIVATE` reads the value as an `int` count of pointers at the object and wakes every waiter on each one; a count of 0 or fewer reads nothing, and a null list fails with `EFAULT`, where FreeBSD's `copyin` would also fail for any other unreadable list. Waiters share the queue of `sceKernelSyncOnAddressWait`. Operation 0 fails with `EOPNOTSUPP` and operations above 26 with `EINVAL`, as in FreeBSD 11; `UMTX_OP_WAIT_UINT` with a value that does not fit in 32 bits returns 0 at once, as FreeBSD's zero-extended compare does. Operation 1 throws, since PPSA12544's call_once uses it in a way FreeBSD rejects; the other operations throw, and so does a misaligned object. PPSA12544's `libc.prx` passes `UMTX_OP_WAIT` and `UMTX_OP_WAIT_UINT` a pointer to a 4-byte microsecond timeout with size argument 0, which FreeBSD reads as a `timespec`, and waits for a running call_once with operation 1; its `PS5Util.prx` passes a `timespec`. Whether the console reads a microsecond timeout there, and what operation 1 does on it, is unverified. A libc call cannot be told apart from a `timespec` call by the size argument alone, so a timeout with size argument 0 is read as a `timespec` and only throws when it is not a valid one (negative seconds, nanoseconds outside [0, 1e9)), which is where FreeBSD would return `EINVAL`; a microsecond value that happens to read as a valid `timespec` is waited on as one
|
||||
- [_umtx_op](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - `UMTX_OP_WAIT`, `UMTX_OP_WAKE`, `UMTX_OP_WAIT_UINT`, `UMTX_OP_WAIT_UINT_PRIVATE`, `UMTX_OP_WAKE_PRIVATE` and `UMTX_OP_NWAKE_PRIVATE` follow FreeBSD 11's [kern_umtx.c](https://github.com/freebsd/freebsd-src/blob/stable/11/sys/kern/kern_umtx.c): a timeout whose size argument is at most 16 is a relative `timespec`, a larger one an `_umtx_time`, and a wake of 0 or fewer wakes one waiter. `UMTX_OP_NWAKE_PRIVATE` reads the value as an `int` count of pointers at the object and wakes every waiter on each one; a count of 0 or fewer reads nothing, and a null list fails with `EFAULT`, where FreeBSD's `copyin` would also fail for any other unreadable list. Waiters share the queue of `sceKernelSyncOnAddressWait`. Operation 0 fails with `EOPNOTSUPP` and operations above 26 with `EINVAL`, as in FreeBSD 11; `UMTX_OP_WAIT_UINT` with a value that does not fit in 32 bits returns 0 at once, as FreeBSD's zero-extended compare does. Operation 1 throws, since PPSA12544's call_once uses it in a way FreeBSD rejects; the other operations throw, and so does a misaligned object. PPSA12544's `libc.prx` and Unity's `PS5Util.prx` (PPSA18194) pass `UMTX_OP_WAIT` and `UMTX_OP_WAIT_UINT` a pointer to a 4-byte microsecond timeout with size argument 0, which FreeBSD reads as a `timespec`, and wait for a running call_once with operation 1. Whether the console reads a microsecond timeout there in all cases, and what operation 1 does on it, is unverified. A call with size argument 0 is read unconditionally as a 4-byte microsecond timeout, matching callers like `libc.prx` and `PS5Util.prx`'s `zlqfTyrQSPk` wrapper which pass size 0 with a 32-bit microsecond count
|
||||
- [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
|
||||
- [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
|
||||
|
||||
Reference in new issue
Block a user