212 lines
6.4 KiB
C++
212 lines
6.4 KiB
C++
// 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"
|
|
|
|
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<boost::asio::steady_timer>(
|
|
io_context, std::chrono::microseconds(total_us - HrTimerSpinlockThresholdUs));
|
|
|
|
event.timer->async_wait(std::bind(SmallTimerCallback, std::placeholders::_1, 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
|