// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" #include "common/debug.h" #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/event_queues.h" #include namespace Libraries::Kernel { extern boost::asio::io_context io_context; extern void KernelSignalRequest(); static constexpr auto HrTimerSpinlockThresholdUs = 1200u; static void SmallTimerCallback(const boost::system::error_code& error, SceKernelEqueue eq, SceKernelEvent kevent) { static EqueueEvent event; event.event = kevent; event.event.data = HrTimerSpinlockThresholdUs; eq->AddSmallTimer(event); eq->TriggerEvent(kevent.ident, SceKernelEvent::Filter::HrTimer, kevent.udata); } int PS4_SYSV_ABI sceKernelCreateEqueue(SceKernelEqueue* eq, const char* name) { if (eq == nullptr) { LOG_ERROR(Kernel_Event, "Event queue is null!"); return ORBIS_KERNEL_ERROR_EINVAL; } if (name == nullptr) { LOG_ERROR(Kernel_Event, "Event queue name is null!"); return ORBIS_KERNEL_ERROR_EINVAL; } // Maximum is 32 including null terminator static constexpr size_t MaxEventQueueNameSize = 32; if (std::strlen(name) > MaxEventQueueNameSize) { LOG_ERROR(Kernel_Event, "Event queue name exceeds 32 bytes!"); return ORBIS_KERNEL_ERROR_ENAMETOOLONG; } LOG_INFO(Kernel_Event, "name = {}", name); *eq = new EqueueInternal; (*eq)->setName(std::string(name)); return ORBIS_OK; } int PS4_SYSV_ABI sceKernelDeleteEqueue(SceKernelEqueue eq) { if (eq == nullptr) { return ORBIS_KERNEL_ERROR_EBADF; } delete eq; return ORBIS_OK; } int PS4_SYSV_ABI sceKernelWaitEqueue(SceKernelEqueue eq, SceKernelEvent* ev, int num, int* out, SceKernelUseconds* timo) { HLE_TRACE; TRACE_HINT(eq->GetName()); LOG_TRACE(Kernel_Event, "equeue = {} num = {}", eq->GetName(), num); if (eq == nullptr) { return ORBIS_KERNEL_ERROR_EBADF; } if (ev == nullptr) { return ORBIS_KERNEL_ERROR_EFAULT; } if (num < 1) { return ORBIS_KERNEL_ERROR_EINVAL; } if (eq->HasSmallTimer()) { ASSERT(timo && *timo); *out = eq->WaitForSmallTimer(ev, num, *timo); } else { if (timo == nullptr) { // wait until an event arrives without timing out *out = eq->WaitForEvents(ev, num, 0); } if (timo != nullptr) { // Only events that have already arrived at the time of this function call can be // received if (*timo == 0) { *out = eq->GetTriggeredEvents(ev, num); } else { // Wait until an event arrives with timing out *out = eq->WaitForEvents(ev, num, *timo); } } } if (*out == 0) { return ORBIS_KERNEL_ERROR_ETIMEDOUT; } return ORBIS_OK; } s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, timespec* ts, void* udata) { if (eq == nullptr) { return ORBIS_KERNEL_ERROR_EBADF; } if (ts->tv_sec > 100 || ts->tv_nsec < 100'000) { return ORBIS_KERNEL_ERROR_EINVAL; } ASSERT(ts->tv_nsec > 1000); // assume 1us resolution const auto total_us = ts->tv_sec * 1000'000 + ts->tv_nsec / 1000; EqueueEvent event{}; event.event.ident = id; event.event.filter = SceKernelEvent::Filter::HrTimer; event.event.flags = SceKernelEvent::Flags::Add | SceKernelEvent::Flags::OneShot; event.event.fflags = 0; event.event.data = total_us; event.event.udata = udata; // HR timers cannot be implemented within the existing event queue architecture due to the // slowness of the notification mechanism. For instance, a 100us timer will lose its precision // as the trigger time drifts by +50-700%, depending on the host PC and workload. To address // this issue, we use a spinlock for small waits (which can be adjusted using // `HrTimerSpinlockThresholdUs`) and fall back to boost asio timers if the time to tick is // large. Even for large delays, we truncate a small portion to complete the wait // using the spinlock, prioritizing precision. if (total_us < HrTimerSpinlockThresholdUs) { return eq->AddSmallTimer(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; } event.timer = std::make_unique( io_context, std::chrono::microseconds(total_us - HrTimerSpinlockThresholdUs)); event.timer->async_wait( std::bind(SmallTimerCallback, boost::asio::placeholders::error, eq, event.event)); if (!eq->AddEvent(event)) { return ORBIS_KERNEL_ERROR_ENOMEM; } KernelSignalRequest(); return ORBIS_OK; } int PS4_SYSV_ABI sceKernelAddUserEvent(SceKernelEqueue eq, int id) { if (eq == nullptr) { return ORBIS_KERNEL_ERROR_EBADF; } EqueueEvent event{}; event.event.ident = id; event.event.filter = SceKernelEvent::Filter::User; event.event.udata = 0; event.event.flags = SceKernelEvent::Flags::Add; event.event.fflags = 0; event.event.data = 0; return eq->AddEvent(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; } int PS4_SYSV_ABI sceKernelAddUserEventEdge(SceKernelEqueue eq, int id) { if (eq == nullptr) { return ORBIS_KERNEL_ERROR_EBADF; } EqueueEvent event{}; event.event.ident = id; event.event.filter = SceKernelEvent::Filter::User; event.event.udata = 0; event.event.flags = SceKernelEvent::Flags::Add | SceKernelEvent::Flags::Clear; event.event.fflags = 0; event.event.data = 0; return eq->AddEvent(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; } void* PS4_SYSV_ABI sceKernelGetEventUserData(const SceKernelEvent* ev) { ASSERT(ev); return ev->udata; } u64 PS4_SYSV_ABI sceKernelGetEventId(const SceKernelEvent* ev) { return ev->ident; } int PS4_SYSV_ABI sceKernelTriggerUserEvent(SceKernelEqueue eq, int id, void* udata) { if (eq == nullptr) { return ORBIS_KERNEL_ERROR_EBADF; } if (!eq->TriggerEvent(id, SceKernelEvent::Filter::User, udata)) { return ORBIS_KERNEL_ERROR_ENOENT; } return ORBIS_OK; } int PS4_SYSV_ABI sceKernelDeleteUserEvent(SceKernelEqueue eq, int id) { if (eq == nullptr) { return ORBIS_KERNEL_ERROR_EBADF; } if (!eq->RemoveEvent(id)) { return ORBIS_KERNEL_ERROR_ENOENT; } return ORBIS_OK; } } // namespace Libraries::Kernel