Files
AnyPS5/core/libs/prx/libkernel/Equeue/Equeue.hpp
T
DavidandClaude Fable 5.1 9ef4aa2c40 feat(libkernel): implement kqueue and kevent over the event queue
Cloudberry Kingdom (PPSA23827, #3177) calls kqueue right after start-up
and stopped on the throwing declarations merged with #3337; Palworld's
Discord module (PPSA23126, #2648) imports kevent.

kqueue reserves a host null-device descriptor in the file descriptor
space and registers it as an event queue that reports each ready event
once per call and moves level-triggered ones behind the others, as
kqueue_scan does. close and sceKernelClose close the queue and drop the
descriptor's events from every queue, dup2 drops them for the target,
and dup/dup2 of a kqueue throw, so a reused descriptor number is never
read as a stale kqueue.

kevent follows FreeBSD 11 kern_event.c (release/11.0.1 34bd3475):
kqueue_kevent for the changelist (filter 0 skipped, EV_SYSFLAGS
cleared, EV_ERROR receipts with the errno in data, EV_RECEIPT, the
early return when nevents is exhausted), kqueue_register for EV_ADD,
EV_DELETE, EV_ENABLE, EBADF, ENOENT and EINVAL, kqueue_scan for the
timeout (EINVAL for a bad timespec, zero polls, above INT32_MAX waits
forever) and filt_timerattach, filt_usertouch for the timer and user
filters. Read and write events reuse the guest socket poll of
sceKernelAddReadEvent/WriteEvent.

Throws: other filters, EV_DISABLE, EV_DISPATCH, EV_FORCEONESHOT,
NOTE_LOWAT, EV_CLEAR on a socket, read/write events on non-socket
descriptors or another kqueue, changes to an existing timer (11.0 and
11.4 differ), a NOTE_NSECONDS fraction above one second and a negative
nevents. Recorded in TechnicalDebt.

Test guest_kqueue covers creation and close, timers, a user event
triggered from another thread, read and write events on a socket pair,
EV_ERROR receipts, the timeout and the error cases.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-12 22:36:59 +02:00

124 lines
4.4 KiB
C++

#ifndef CORE_LIBS_PRX_LIBKERNEL_EQUEUE_EQUEUE_HPP
#define CORE_LIBS_PRX_LIBKERNEL_EQUEUE_EQUEUE_HPP
#include <cstdint>
#include <deque>
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libkernel/KernelErrors.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
static constexpr int16_t EVFILT_READ = -1;
static constexpr int16_t EVFILT_WRITE = -2;
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;
static constexpr int16_t EVFILT_AMPR = -25;
static constexpr uint16_t EV_ADD = 0x0001;
static constexpr uint16_t EV_DELETE = 0x0002;
static constexpr uint16_t EV_ENABLE = 0x0004;
static constexpr uint16_t EV_DISABLE = 0x0008;
static constexpr uint16_t EV_ONESHOT = 0x0010;
static constexpr uint16_t EV_CLEAR = 0x0020;
static constexpr uint16_t EV_RECEIPT = 0x0040;
static constexpr uint16_t EV_DISPATCH = 0x0080;
static constexpr uint16_t EV_FORCEONESHOT = 0x0100;
static constexpr uint16_t EV_SYSFLAGS = 0xF000;
static constexpr uint16_t EV_ERROR = 0x4000;
static constexpr uint16_t EV_EOF = 0x8000;
static constexpr int EQUEUE_OK = 0;
struct KernelEqueueEvent;
using EqueueTriggerFunc = void (*)(KernelEqueueEvent* event, void* triggerData);
using EqueueResetFunc = void (*)(KernelEqueueEvent* event);
using EqueueDeleteFunc = void (*)(KernelEqueue eq, KernelEqueueEvent* event);
using EqueuePollFunc = bool (*)(KernelEqueueEvent* event);
struct KernelFilter {
void* data = nullptr;
std::shared_ptr<void> owner;
EqueueTriggerFunc triggerFunc = nullptr;
EqueueResetFunc resetFunc = nullptr;
EqueueDeleteFunc deleteEventFunc = nullptr;
EqueuePollFunc pollFunc = nullptr;
};
struct KernelEqueueEvent {
bool triggered = false;
uint64_t deadlineNs = 0;
uint64_t intervalNs = 0;
KernelEvent event;
KernelFilter filter;
std::deque<KernelEvent> pendingEvents;
};
class KernelEqueuePrivate {
public:
explicit KernelEqueuePrivate(KernelEqueue handle, bool reportOncePerWait = false);
~KernelEqueuePrivate();
KernelEqueuePrivate(const KernelEqueuePrivate&) = delete;
KernelEqueuePrivate& operator=(const KernelEqueuePrivate&) = delete;
const std::string& GetName() const { return m_name; }
void SetName(std::string name) { m_name = std::move(name); }
int AddEvent(const KernelEqueueEvent& event);
int TriggerEvent(uintptr_t ident, int16_t filter, void* triggerData);
int DeleteEvent(uintptr_t ident, int16_t filter);
bool HasEvent(uintptr_t ident, int16_t filter);
int EditEvent(uintptr_t ident, int16_t filter, const std::function<void(KernelEqueueEvent&)>& edit);
int GetTriggeredEvents(KernelEvent* ev, int num);
int WaitForEvents(KernelEvent* ev, int num, uint32_t micros);
int WaitForEventsUntil(KernelEvent* ev, int num, std::uint64_t deadlineNanos);
void RemoveDescriptorEvents(uintptr_t ident);
void Close();
static uint64_t MonotonicNs();
private:
void TriggerExpiredTimers(uint64_t nowNs);
bool NextTimerWaitMicros(uint64_t nowNs, uint32_t* out) const;
int CollectTriggered(KernelEvent* ev, int num);
int CollectOncePerWait(KernelEvent* ev, int num);
bool PollEvents();
void WakePollers();
void WaitForDescriptors(std::unique_lock<std::mutex>& lock, std::uint64_t deadlineNanos);
std::list<KernelEqueueEvent> m_events;
std::vector<std::uintptr_t> m_pollers;
std::mutex m_mutex;
TimedWait::Condition m_cond;
std::string m_name;
KernelEqueue m_handle;
bool m_reportOncePerWait;
bool m_closed = false;
};
using KernelEqueueRef = std::shared_ptr<KernelEqueuePrivate>;
void EqueueDescriptorClosed(int descriptor);
void KqueueDescriptorClosed(int descriptor);
bool IsKqueueDescriptor(int descriptor);
KernelEqueueEvent EqueueDescriptorEvent(int descriptor, int16_t filter, uint16_t flags, void* udata);
extern "C" {
KernelEqueueRef EqueuePin_nid_postfix(KernelEqueue eq);
int APS5_VABI EqueueAddEvent_nid_postfix(KernelEqueue eq, const KernelEqueueEvent& event);
int APS5_VABI EqueueTriggerEvent_nid_postfix(KernelEqueue eq, uintptr_t ident, int16_t filter, void* triggerData);
int APS5_VABI EqueueDeleteEvent_nid_postfix(KernelEqueue eq, uintptr_t ident, int16_t filter);
}
#endif