shadPS4/src/core/libraries/kernel/equeue.h
Stephen Miller e0850d5cfc
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
Kernel.Equeue: Only reset trigger state on events that clear. (#4032)
* Don't clear events that don't need clearing

Unless the event has the clear flag, it will be returned multiple times after triggering.

* Fix event flags

As older code suggests, the PS4 kernel does append the clear flag to various event types internally. This is visible when observing the returned event data from sceKernelWaitEqueue (or kevent, if you're feeling ambitious)

Add flag is also removed from events internally.
2026-02-14 01:59:23 +02:00

187 lines
4.6 KiB
C++

// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include <mutex>
#include <string>
#include <vector>
#include <boost/asio/steady_timer.hpp>
#include <unordered_map>
#include "common/rdtsc.h"
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Kernel {
class EqueueInternal;
struct EqueueEvent;
using SceKernelUseconds = u32;
using SceKernelEqueue = EqueueInternal*;
struct SceKernelEvent {
enum Filter : s16 {
None = 0,
Read = -1,
Write = -2,
Aio = -3,
Vnode = -4,
Proc = -5,
Signal = -6,
Timer = -7,
Fs = -9,
Lio = -10,
User = -11,
Polling = -12,
VideoOut = -13,
GraphicsCore = -14,
HrTimer = -15,
};
enum Flags : u16 {
Add = 1u,
Delete = 2u,
Enable = 4u,
Disable = 8u,
OneShot = 0x10u,
Clear = 0x20u,
Receipt = 0x40u,
Dispatch = 0x80u,
Flag1 = 0x2000u,
System = 0xf000u,
};
u64 ident = 0; /* identifier for this event */
Filter filter = Filter::None; /* filter for event */
u16 flags = 0;
u32 fflags = 0;
u64 data = 0;
void* udata = nullptr; /* opaque user data identifier */
};
struct OrbisVideoOutEventHint {
u64 event_id : 8;
u64 video_id : 8;
u64 flip_arg : 48;
};
struct OrbisVideoOutEventData {
u64 time : 12;
u64 count : 4;
u64 flip_arg : 48;
};
struct EqueueEvent {
SceKernelEvent event;
void* data = nullptr;
std::chrono::steady_clock::time_point time_added;
std::chrono::microseconds timer_interval;
std::unique_ptr<boost::asio::steady_timer> timer;
void Clear() {
is_triggered = false;
event.fflags = 0;
event.data = 0;
}
void Trigger(void* data) {
is_triggered = true;
event.fflags++;
event.data = reinterpret_cast<uintptr_t>(data);
}
void TriggerUser(void* data) {
is_triggered = true;
event.fflags++;
event.udata = data;
}
void TriggerDisplay(void* data) {
is_triggered = true;
if (data != nullptr) {
auto event_data = static_cast<OrbisVideoOutEventData>(event.data);
auto event_hint_raw = reinterpret_cast<u64>(data);
auto event_hint = static_cast<OrbisVideoOutEventHint>(event_hint_raw);
if (event_hint.event_id == event.ident && event.ident != 0xfe) {
auto time = Common::FencedRDTSC();
auto counter = event_data.count;
if (counter != 0xf) {
counter++;
}
event.data =
(time & 0xfff) | (counter << 0xc) | (event_hint_raw & 0xffffffffffff0000);
}
}
}
bool IsTriggered() const {
return is_triggered;
}
bool operator==(const EqueueEvent& ev) const {
return ev.event.ident == event.ident && ev.event.filter == event.filter;
}
private:
bool is_triggered = false;
};
class EqueueInternal {
struct SmallTimer {
SceKernelEvent event;
std::chrono::steady_clock::time_point added;
std::chrono::microseconds interval;
};
public:
explicit EqueueInternal(std::string_view name) : m_name(name) {}
std::string_view GetName() const {
return m_name;
}
bool AddEvent(EqueueEvent& event);
bool ScheduleEvent(u64 id, s16 filter,
void (*callback)(SceKernelEqueue, const SceKernelEvent&));
bool RemoveEvent(u64 id, s16 filter);
int WaitForEvents(SceKernelEvent* ev, int num, const SceKernelUseconds* timo);
bool TriggerEvent(u64 ident, s16 filter, void* trigger_data);
int GetTriggeredEvents(SceKernelEvent* ev, int num);
bool AddSmallTimer(EqueueEvent& event);
bool HasSmallTimer() {
std::scoped_lock lock{m_mutex};
return !m_small_timers.empty();
}
bool RemoveSmallTimer(u64 id) {
if (HasSmallTimer()) {
std::scoped_lock lock{m_mutex};
return m_small_timers.erase(id) > 0;
}
return false;
}
int WaitForSmallTimer(SceKernelEvent* ev, int num, u32 micros);
bool EventExists(u64 id, s16 filter);
private:
std::string m_name;
std::mutex m_mutex;
std::vector<EqueueEvent> m_events;
std::condition_variable m_cond;
std::unordered_map<u64, SmallTimer> m_small_timers;
};
u64 PS4_SYSV_ABI sceKernelGetEventData(const SceKernelEvent* ev);
void RegisterEventQueue(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Kernel