Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions core/libs/prx/libkernel/Equeue/Equeue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ void KernelEqueuePrivate::Close() {

void KernelEqueuePrivate::TriggerExpiredTimers(uint64_t nowNs) {
for (auto& ev : m_events) {
if (!ev.triggered && ev.deadlineNs != 0 && ev.deadlineNs <= nowNs) {
if (ev.deadlineNs != 0 && ev.deadlineNs <= nowNs) {
if (ev.event.filter == EVFILT_TIMER) {
const uint64_t count = ev.intervalNs == 0
? (ev.triggered ? 0 : 1)
: 1 + (nowNs - ev.deadlineNs) / ev.intervalNs;
ev.event.data += static_cast<intptr_t>(count);
ev.deadlineNs += count * ev.intervalNs;
}
ev.triggered = true;
}
}
Expand Down Expand Up @@ -151,7 +158,12 @@ int KernelEqueuePrivate::WaitForEvents(KernelEvent* ev, int num, uint32_t micros
return SCE_KERNEL_ERROR_EBADF;
}
if (micros == 0) {
m_cond.Wait(lock);
uint32_t timerWait = 0;
if (NextTimerWaitMicros(MonotonicNs(), &timerWait)) {
m_cond.WaitUntil(lock, TimedWait::NowNanos() + static_cast<std::uint64_t>(timerWait) * 1000ULL);
} else {
m_cond.Wait(lock);
}
} else {
uint32_t timerWait = 0;
const bool hasTimer = NextTimerWaitMicros(MonotonicNs(), &timerWait);
Expand All @@ -176,7 +188,11 @@ int KernelEqueuePrivate::AddEvent(const KernelEqueueEvent& event) {
}
);
if (it != m_events.end()) {
if (event.event.filter == EVFILT_TIMER) {
TriggerExpiredTimers(MonotonicNs());
}
it->deadlineNs = event.deadlineNs;
it->intervalNs = event.intervalNs;
it->event.udata = event.event.udata;
for (auto& pending : it->pendingEvents) {
pending.udata = event.event.udata;
Expand Down Expand Up @@ -403,6 +419,22 @@ int APS5_VABI sceKernelDeleteHRTimerEvent(KernelEqueue eq, int id) {
return EqueueDeleteEvent_nid_postfix(eq, static_cast<uintptr_t>(id), EVFILT_HRTIMER);
}

int APS5_VABI sceKernelAddTimerEvent(KernelEqueue eq, int id, KernelUseconds usec, void* udata) {
const uint64_t intervalNs = static_cast<uint64_t>(usec) * 1000ULL;
KernelEqueueEvent event{};
event.deadlineNs = KernelEqueuePrivate::MonotonicNs() + intervalNs;
event.intervalNs = intervalNs;
event.event.ident = static_cast<uintptr_t>(id);
event.event.filter = EVFILT_TIMER;
event.event.flags = EV_ADD | EV_CLEAR;
event.event.udata = udata;
return EqueueAddEvent_nid_postfix(eq, event);
}

int APS5_VABI sceKernelDeleteTimerEvent(KernelEqueue eq, int id) {
return EqueueDeleteEvent_nid_postfix(eq, static_cast<uintptr_t>(id), EVFILT_TIMER);
}

int APS5_VABI sceKernelAddAmprEvent(KernelEqueue eq, int id, void* udata) {
if (eq == 0) {
return EQUEUE_OK;
Expand Down
4 changes: 3 additions & 1 deletion core/libs/prx/libkernel/Equeue/Equeue.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "prx/libkernel/KernelErrors.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"

static constexpr int16_t EVFILT_TIMER = -7;
static constexpr int16_t EVFILT_USER = -11;
static constexpr int16_t EVFILT_VIDEO_OUT = -13;
static constexpr int16_t EVFILT_HRTIMER = -15;
Expand Down Expand Up @@ -42,6 +43,7 @@ struct KernelFilter {
struct KernelEqueueEvent {
bool triggered = false;
uint64_t deadlineNs = 0;
uint64_t intervalNs = 0;
KernelEvent event;
KernelFilter filter;
std::deque<KernelEvent> pendingEvents;
Expand All @@ -64,9 +66,9 @@ class KernelEqueuePrivate {
int GetTriggeredEvents(KernelEvent* ev, int num);
int WaitForEvents(KernelEvent* ev, int num, uint32_t micros);
void Close();
static uint64_t MonotonicNs();

private:
static uint64_t MonotonicNs();
void TriggerExpiredTimers(uint64_t nowNs);
bool NextTimerWaitMicros(uint64_t nowNs, uint32_t* out) const;

Expand Down
92 changes: 92 additions & 0 deletions core/libs/tests/GuestEqueueEvents.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#include "SceTypes.hpp"
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <thread>

extern "C" {
int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name);
Expand All @@ -11,6 +13,8 @@ int APS5_VABI sceKernelAddUserEvent(KernelEqueue eq, int id);
int APS5_VABI sceKernelTriggerUserEvent(KernelEqueue eq, int id, void* udata);
int APS5_VABI sceKernelDeleteUserEvent(KernelEqueue eq, int id);
int APS5_VABI sceKernelAddHRTimerEvent(KernelEqueue eq, int id, const KernelTimespec* ts, void* udata);
int APS5_VABI sceKernelAddTimerEvent(KernelEqueue eq, int id, KernelUseconds usec, void* udata);
int APS5_VABI sceKernelDeleteTimerEvent(KernelEqueue eq, int id);
intptr_t APS5_VABI sceKernelGetEventData(const KernelEvent* ev);
intptr_t APS5_VABI sceKernelGetEventFflags(const KernelEvent* ev);
int APS5_VABI sceKernelGetEventFilter(const KernelEvent* ev);
Expand All @@ -19,6 +23,10 @@ void* APS5_VABI sceKernelGetEventUserData(const KernelEvent* ev);
}

static constexpr int SCE_OK = 0;
static constexpr int SCE_KERNEL_ERROR_ENOENT = static_cast<int>(0x80020002);
static constexpr int SCE_KERNEL_ERROR_EBADF = static_cast<int>(0x80020009);
static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast<int>(0x8002003c);
static constexpr int EVFILT_TIMER = -7;
static constexpr int EVFILT_USER = -11;
static constexpr int EVFILT_HRTIMER = -15;

Expand All @@ -34,7 +42,91 @@ static bool RejectsNull(TResult (APS5_VABI *accessor)(const KernelEvent*)) {
return false;
}

static void SleepAtLeast(std::chrono::steady_clock::duration duration) {
const auto start = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - start < duration) {
std::this_thread::sleep_for(duration);
}
}

static void VerifyPeriodicTimer() {
using namespace std::chrono_literals;
int first = 0;
int second = 0;
Require(sceKernelAddTimerEvent(0, 10, 1000, &first) == SCE_KERNEL_ERROR_EBADF);
Require(sceKernelDeleteTimerEvent(0, 10) == SCE_KERNEL_ERROR_EBADF);

KernelEqueue eq = 0;
Require(sceKernelCreateEqueue(&eq, "timer") == SCE_OK);
Require(sceKernelDeleteTimerEvent(eq, 10) == SCE_KERNEL_ERROR_ENOENT);

KernelEvent events[2]{};
int count = 0;
const KernelUseconds poll = 0;
const KernelUseconds wait = 1000000;
Require(sceKernelAddTimerEvent(eq, 10, 1000, &first) == SCE_OK);
SleepAtLeast(5ms);
Require(sceKernelWaitEqueue(eq, events, 2, &count, &poll) == SCE_OK);
Require(count == 1);
Require(sceKernelGetEventId(&events[0]) == 10);
Require(sceKernelGetEventFilter(&events[0]) == EVFILT_TIMER);
Require(sceKernelGetEventData(&events[0]) >= 5);
Require(sceKernelGetEventUserData(&events[0]) == &first);
Require(sceKernelWaitEqueue(eq, events, 2, &count, &wait) == SCE_OK);
Require(count == 1 && sceKernelGetEventData(&events[0]) >= 1);

SleepAtLeast(5ms);
Require(sceKernelAddTimerEvent(eq, 10, 3000000000u, &second) == SCE_OK);
Require(sceKernelWaitEqueue(eq, events, 2, &count, &poll) == SCE_OK);
Require(count == 1);
Require(sceKernelGetEventData(&events[0]) >= 5);
Require(sceKernelGetEventUserData(&events[0]) == &second);
Require(sceKernelWaitEqueue(eq, events, 2, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT);
Require(sceKernelDeleteTimerEvent(eq, 10) == SCE_OK);
Require(sceKernelDeleteTimerEvent(eq, 10) == SCE_KERNEL_ERROR_ENOENT);

Require(sceKernelAddTimerEvent(eq, 13, 3000000000u, &first) == SCE_OK);
const auto readded = std::chrono::steady_clock::now();
Require(sceKernelAddTimerEvent(eq, 13, 200000, &first) == SCE_OK);
SleepAtLeast(250ms);
Require(sceKernelWaitEqueue(eq, events, 2, &count, &poll) == SCE_OK);
Require(count == 1 && sceKernelGetEventData(&events[0]) >= 1);
const intptr_t expirations = sceKernelGetEventData(&events[0]);
const int again = sceKernelWaitEqueue(eq, events, 2, &count, &poll);
if (std::chrono::steady_clock::now() - readded < 400ms) {
Require(expirations == 1 && again == SCE_KERNEL_ERROR_ETIMEDOUT);
}
Require(sceKernelWaitEqueue(eq, events, 2, &count, &wait) == SCE_OK);
Require(count == 1 && sceKernelGetEventId(&events[0]) == 13);
Require(sceKernelDeleteTimerEvent(eq, 13) == SCE_OK);

Require(sceKernelAddTimerEvent(eq, 14, 1000, &first) == SCE_OK);
Require(sceKernelWaitEqueue(eq, events, 2, &count, nullptr) == SCE_OK);
Require(count == 1 && sceKernelGetEventId(&events[0]) == 14 && sceKernelGetEventData(&events[0]) >= 1);
Require(sceKernelDeleteTimerEvent(eq, 14) == SCE_OK);
const KernelTimespec soon{0, 1000000};
Require(sceKernelAddHRTimerEvent(eq, 15, &soon, &second) == SCE_OK);
Require(sceKernelWaitEqueue(eq, events, 2, &count, nullptr) == SCE_OK);
Require(count == 1 && sceKernelGetEventId(&events[0]) == 15 && sceKernelGetEventFilter(&events[0]) == EVFILT_HRTIMER);

Require(sceKernelAddTimerEvent(eq, 11, 0, &first) == SCE_OK);
Require(sceKernelAddTimerEvent(eq, 11, 0, &first) == SCE_OK);
for (int i = 0; i < 2; ++i) {
Require(sceKernelWaitEqueue(eq, events, 2, &count, &poll) == SCE_OK);
Require(count == 1 && sceKernelGetEventData(&events[0]) == 1);
}
Require(sceKernelDeleteTimerEvent(eq, 11) == SCE_OK);
Require(sceKernelWaitEqueue(eq, events, 2, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT);

const KernelTimespec delay{0, 1000000};
Require(sceKernelAddHRTimerEvent(eq, 12, &delay, &first) == SCE_OK);
Require(sceKernelDeleteTimerEvent(eq, 12) == SCE_KERNEL_ERROR_ENOENT);
Require(sceKernelDeleteEqueue(eq) == SCE_OK);
}

int main() {
VerifyPeriodicTimer();

KernelEqueue eq = 0;
Require(sceKernelCreateEqueue(&eq, "events") == SCE_OK);
const KernelUseconds timeout = 1000000;
Expand Down
1 change: 1 addition & 0 deletions docs/dev/TechnicalDebt.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ Throughout the project, every function at every stage either **does exactly what
- [sceVdecswResetDecoder](../../core/libs/prx/libSceVdecsw/Export.cpp) (libSceVdecsw) - unknown signature
- [sceCoredumpAttachUserMemoryFile](../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel) - unknown signature
- [sceCoredumpAttachMemoryRegion](../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel) - unknown signature
- [sceKernelAddTimerEvent](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) - `usec` is taken as the period in microseconds; re-adding an existing timer keeps its pending expiration count and restarts the period from the time of the call, where FreeBSD 12+ `filt_timertouch` clears pending expirations on `EV_ADD`; `usec == 0` fires on every wait. None of this is confirmed on a PS5 title
- [sceRtcFormatRFC2822, sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - only shadPS4 (PS4) has bodies. A null tick returns `INVALID_POINTER` like the existing `sceRtcFormatRFC3339`, where shadPS4 formats the current time instead; offsets outside ±1439 minutes return `INVALID_VALUE` like `sceRtcFormatRFC3339`; the local-time variants use the host's offset at the given instant (shadPS4 uses `sceKernelGettimezone`, the current offset)
- [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf`
- [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown.
Expand Down
Loading