Files
PS2Recomp/ps2xRuntime/include/runtime/ee_scheduler.h
T
Ranieri 8d7e8a5a46 Feature/ee timers and fixes (#203)
* refactor: from guest  threads to EE scheduler

* feat: bad wip mpeg fix for code veronica

* feat: cheap copy from host
feat: small perf o vsync tick

* feat: added EE clock Hz
fix: fix MPEG out of sync with new EE refactor

* fix: fix lotr tests

* fix: fix cri dtx loading
fix: fix wrong mmi instruction translation
fix: fix thread info params
feat: added EE  timers decoder and consumer
feat: split SFI and IOP memory to prevent collision and overrides

* feat: revert wrong changes
2026-08-12 12:11:06 -03:00

450 lines
13 KiB
C++

#pragma once
#include "ps2_runtime.h"
#include <array>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <functional>
#include <mutex>
#include <optional>
#include <thread>
#include <unordered_map>
#include <variant>
#include <vector>
// This exception is the EE equivalent of a longjmp to the dispatcher. It is
// not an error and must only be caught at EeScheduler::run().
struct EeDispatcherTransfer final
{
};
enum class EeThreadStatus : uint8_t
{
Running,
Ready,
Waiting,
WaitingSuspended,
Suspended,
Dormant,
};
enum class EeWaitReason : uint8_t
{
None,
Sleep,
Semaphore,
EventFlag,
VSync,
External,
Mpeg,
};
struct EeSemaphoreWait
{
int id = 0;
};
struct EeEventFlagWait
{
int id = 0;
uint32_t bits = 0;
uint32_t mode = 0;
uint32_t resultAddress = 0;
};
struct EeVSyncWait
{
uint64_t afterTick = 0;
int fixedResult = -1;
};
struct EeExternalWait
{
uint32_t type = 0;
uint64_t token = 0;
};
using EeWaitPayload = std::variant<std::monostate,
EeSemaphoreWait,
EeEventFlagWait,
EeVSyncWait,
EeExternalWait>;
struct EeWaitState
{
EeWaitReason reason = EeWaitReason::None;
EeWaitPayload payload{};
std::function<void(R5900Context &)> completion;
};
enum class GuestInvocationKind : uint8_t
{
Interrupt,
Alarm,
GsCallback,
RpcCallback,
SyscallOverride,
ExitHandler,
HleCall,
};
struct GuestInvocation
{
GuestInvocationKind kind = GuestInvocationKind::Interrupt;
uint64_t sequence = 0;
uint64_t tag = 0;
R5900Context context{};
std::function<void(const R5900Context &, R5900Context &)> onComplete;
};
struct GuestThread
{
int id = 0;
R5900Context context{};
uint32_t entry = 0;
uint32_t stack = 0;
uint32_t stackSize = 0;
uint32_t gp = 0;
uint32_t attr = 0;
uint32_t option = 0;
uint32_t arg = 0;
int initialPriority = 0;
int currentPriority = 0;
EeThreadStatus status = EeThreadStatus::Dormant;
int suspendCount = 0;
uint32_t wakeupCount = 0;
bool ownsStack = false;
uint32_t tlsBase = 0;
EeWaitState wait{};
std::function<void(R5900Context &)> resumeCompletion;
std::vector<GuestInvocation> invocations;
[[nodiscard]] R5900Context &activeContext()
{
return invocations.empty() ? context : invocations.back().context;
}
[[nodiscard]] const R5900Context &activeContext() const
{
return invocations.empty() ? context : invocations.back().context;
}
};
struct EeSemaphore
{
int id = 0;
int count = 0;
int maxCount = 0;
int initCount = 0;
uint32_t attr = 0;
uint32_t option = 0;
std::deque<int> waiters;
};
struct EeEventFlag
{
int id = 0;
uint32_t attr = 0;
uint32_t option = 0;
uint32_t initBits = 0;
uint32_t bits = 0;
std::deque<int> waiters;
};
struct EeAlarm
{
int id = 0;
uint16_t ticks = 0;
uint32_t handler = 0;
uint32_t argument = 0;
uint32_t gp = 0;
uint32_t sp = 0;
};
struct EeIrqHandler
{
int id = 0;
uint32_t cause = 0;
uint32_t handler = 0;
uint32_t argument = 0;
uint32_t gp = 0;
uint32_t sp = 0;
bool enabled = true;
int order = 0;
};
struct EeThreadSnapshot
{
int id = 0;
uint32_t pc = 0;
uint32_t entry = 0;
uint32_t stack = 0;
uint32_t stackSize = 0;
uint32_t gp = 0;
int initialPriority = 0;
int currentPriority = 0;
EeThreadStatus status = EeThreadStatus::Dormant;
EeWaitReason waitReason = EeWaitReason::None;
int waitId = 0;
int suspendCount = 0;
uint32_t wakeupCount = 0;
};
struct EeSemaphoreSnapshot
{
int id = 0;
int count = 0;
int maxCount = 0;
uint32_t waiters = 0;
};
struct EeEventFlagSnapshot
{
int id = 0;
uint32_t bits = 0;
uint32_t initBits = 0;
uint32_t attr = 0;
uint32_t waiters = 0;
};
struct EeKernelSnapshot
{
uint64_t sequence = 0;
uint64_t eeCycle = 0;
uint64_t sliceEndCycle = 0;
uint64_t nextEventCycle = 0;
int runningThreadId = 0;
std::vector<EeThreadSnapshot> threads;
std::vector<EeSemaphoreSnapshot> semaphores;
std::vector<EeEventFlagSnapshot> eventFlags;
};
enum class EeEventType : uint8_t
{
Stop,
VBlankStart,
VBlankEnd,
Dmac,
ExternalWake,
Alarm,
};
struct EeEvent
{
EeEventType type = EeEventType::ExternalWake;
uint32_t id = 0;
uint64_t value = 0;
};
struct EeThreadCreateParams
{
uint32_t attr = 0;
uint32_t entry = 0;
uint32_t stack = 0;
uint32_t stackSize = 0;
uint32_t gp = 0;
int priority = 0;
uint32_t option = 0;
};
class EeScheduler
{
public:
static constexpr int kMainThreadId = 1;
static constexpr int kFirstThreadId = 2;
static constexpr int kLastThreadId = 255;
static constexpr int kPriorityCount = 128;
static constexpr uint64_t kEeClockHz = 294912000ull;
static constexpr uint32_t kGeneratedCheckpointCycles = 32u;
static constexpr uint32_t kGuestDispatchCycles = 8u;
static constexpr uint64_t kDefaultTimeSliceCycles = 65536ull;
explicit EeScheduler(PS2Runtime &runtime);
~EeScheduler();
EeScheduler(const EeScheduler &) = delete;
EeScheduler &operator=(const EeScheduler &) = delete;
void reset(uint8_t *rdram, const R5900Context &mainContext);
void run();
void requestStop();
void postEvent(EeEvent event);
[[nodiscard]] bool checkpointDue(uint32_t cycles = kGeneratedCheckpointCycles) noexcept;
void accountCycles(uint32_t cycles) noexcept;
[[nodiscard]] bool isExecutingGuest() const noexcept;
// Kernel object API. All calls except postEvent/requestStop execute on the
// EE executor and therefore need no host synchronization.
void setupCurrentThread(uint32_t stack, uint32_t stackSize, uint32_t gp);
int createThread(const EeThreadCreateParams &params);
int deleteThread(int id, uint32_t &ownedStack);
int startThread(int id, uint32_t arg, const R5900Context &caller, bool interruptSafe);
[[noreturn]] void exitCurrent(bool deleteThread);
int terminateThread(int id, uint32_t &ownedStack, bool interruptSafe);
int suspendThread(int id, bool interruptSafe);
int resumeThread(int id, bool interruptSafe);
void sleepCurrent();
int wakeupThread(int id, bool interruptSafe);
int cancelWakeup(int id);
int changePriority(int id, int priority, bool interruptSafe, int &oldPriority);
int rotateReadyQueue(int priority, bool interruptSafe);
int releaseWait(int id, bool interruptSafe);
void transferIfRequested(bool interruptSafe);
int createSemaphore(int initCount, int maxCount, uint32_t attr, uint32_t option);
int deleteSemaphore(int id, bool interruptSafe);
int signalSemaphore(int id, bool interruptSafe);
int pollSemaphore(int id);
void waitSemaphore(int id);
int createEventFlag(uint32_t initialBits, uint32_t attr, uint32_t option);
int deleteEventFlag(int id, bool interruptSafe);
int setEventFlag(int id, uint32_t bits, bool interruptSafe);
int clearEventFlag(int id, uint32_t mask);
int pollEventFlag(int id, uint32_t bits, uint32_t mode, uint32_t &observedBits);
void waitEventFlag(int id, uint32_t bits, uint32_t mode, uint32_t resultAddress);
int setAlarm(uint16_t ticks, uint32_t handler, uint32_t argument, uint32_t gp, uint32_t sp);
int cancelAlarm(int id);
void queueInvocation(GuestInvocation invocation);
[[noreturn]] void invokeCurrent(GuestInvocation invocation);
[[noreturn]] void invokeCurrentSequence(std::vector<GuestInvocation> invocations);
[[nodiscard]] bool hasInvocation(GuestInvocationKind kind, uint64_t tag) const;
[[nodiscard]] uint32_t invocationStackTop();
int addIrqHandler(bool dmac,
uint32_t cause,
uint32_t handler,
bool append,
uint32_t argument,
uint32_t gp,
uint32_t sp);
int removeIrqHandler(bool dmac, uint32_t cause, int id);
int setIrqHandlerEnabled(bool dmac, int id, bool enabled);
int setIrqCauseEnabled(bool dmac, uint32_t cause, bool enabled);
void dispatchIrq(bool dmac, uint32_t cause);
void setVSyncFlag(uint32_t flagAddress, uint32_t tickAddress);
[[nodiscard]] uint64_t currentVSyncTick() const noexcept;
uint32_t setGsVSyncCallback(uint32_t callback, uint32_t gp, uint32_t sp);
[[noreturn]] void waitVSync(uint64_t afterTick, int fixedResult = -1, std::function<void(R5900Context &)> completion = {});
void completeVSync(uint64_t tick);
void completeExternalWait(uint32_t type, uint64_t token, int result);
[[noreturn]] void waitExternal(EeWaitReason reason, uint32_t type, uint64_t token, std::function<void(R5900Context &)> completion = {});
[[nodiscard]] GuestThread *thread(int id);
[[nodiscard]] const GuestThread *thread(int id) const;
[[nodiscard]] EeSemaphore *semaphore(int id);
[[nodiscard]] const EeSemaphore *semaphore(int id) const;
[[nodiscard]] EeEventFlag *eventFlag(int id);
[[nodiscard]] const EeEventFlag *eventFlag(int id) const;
[[nodiscard]] GuestThread *currentThread();
[[nodiscard]] const GuestThread *currentThread() const;
[[nodiscard]] int currentThreadId() const noexcept;
[[nodiscard]] R5900Context *currentContext();
[[nodiscard]] uint8_t *rdram() const noexcept;
// Direct syscall tests use the same main-thread record without starting a
// second executor. Production execution calls reset() before run().
void bindMainContextForSyscall(R5900Context &ctx, uint8_t *rdram);
[[nodiscard]] EeKernelSnapshot snapshot() const;
void publishSnapshot();
private:
struct ScheduledEvent
{
uint64_t deadlineCycle = 0;
std::chrono::steady_clock::time_point hostDeadline{};
EeEvent event{};
uint64_t sequence = 0;
};
void assertExecutor() const;
[[nodiscard]] int allocateThreadId();
GuestThread &acquireInvocationThread();
void enqueueReady(GuestThread &thread, bool front = false);
void removeReady(GuestThread &thread);
[[nodiscard]] GuestThread *selectReady();
void makeRunning(GuestThread &thread);
void makeDormant(GuestThread &thread);
void removeFromWaitObject(GuestThread &thread);
[[noreturn]] void blockCurrent(EeWaitState wait);
void makeReady(GuestThread &thread, int result, bool interruptSafe);
void requestPreemptionIfHigher(const GuestThread &readyThread, bool interruptSafe);
void applyPendingPreemption();
void processPendingEvents();
void processDueDeadlines();
void processEvent(const EeEvent &event);
void finishEventWaiters(EeEventFlag &flag, bool interruptSafe);
[[nodiscard]] static bool eventCondition(uint32_t current, uint32_t requested, uint32_t mode);
static int waitObjectId(const EeWaitState &wait);
void writeGuestU32(uint32_t address, uint32_t value);
void waitForEvent();
void scheduleEvent(uint64_t deadlineCycle, std::chrono::steady_clock::time_point hostDeadline, EeEvent event);
void updateNextDeadline();
[[nodiscard]] bool hasReadyAtOrAbovePriority(int priority) const;
void renewTimeSlice();
void copyMainContextToRuntime();
PS2Runtime &m_runtime;
uint8_t *m_rdram = nullptr;
std::array<std::deque<int>, kPriorityCount> m_readyQueues{};
std::unordered_map<int, GuestThread> m_threads;
std::unordered_map<int, EeSemaphore> m_semaphores;
std::unordered_map<int, EeEventFlag> m_eventFlags;
std::unordered_map<int, EeAlarm> m_alarms;
std::unordered_map<int, EeIrqHandler> m_intcHandlers;
std::unordered_map<int, EeIrqHandler> m_dmacHandlers;
int m_nextThreadId = kFirstThreadId;
int m_nextInvocationThreadId = -1;
int m_nextSemaphoreId = 1;
int m_nextEventFlagId = 1;
int m_nextAlarmId = 1;
int m_nextIntcHandlerId = 1;
int m_nextDmacHandlerId = 1;
int m_intcHeadOrder = 0;
int m_intcTailOrder = 1000;
int m_dmacHeadOrder = 0;
int m_dmacTailOrder = 1000;
uint32_t m_enabledIntcMask = 0xFFFFFFFFu;
uint32_t m_enabledDmacMask = 0xFFFFFFFFu;
int m_currentThreadId = 0;
bool m_rescheduleRequested = false;
bool m_timeSliceExpired = false;
bool m_insideInterrupt = false;
uint32_t m_pendingEeTimerInterrupts = 0;
uint64_t m_eeCycle = 0;
uint64_t m_sliceEndCycle = kDefaultTimeSliceCycles;
std::thread::id m_executorThread{};
std::atomic<bool> m_running{false};
std::atomic<bool> m_guestExecuting{false};
std::atomic<bool> m_stopRequested{false};
std::atomic<bool> m_checkpointPending{false};
uint32_t m_debugPublishCountdown = 0u;
mutable std::mutex m_eventMutex;
std::condition_variable m_eventCv;
std::deque<EeEvent> m_events;
std::vector<ScheduledEvent> m_deadlines;
std::deque<GuestInvocation> m_pendingInvocations;
uint64_t m_eventSequence = 0;
uint64_t m_invocationSequence = 0;
uint64_t m_vsyncTick = 0;
uint32_t m_vsyncFlagAddress = 0;
uint32_t m_vsyncTickAddress = 0;
uint32_t m_gsVSyncCallback = 0;
uint32_t m_gsVSyncCallbackGp = 0;
uint32_t m_gsVSyncCallbackSp = 0;
std::unordered_map<uint64_t, uint32_t> m_invocationStackTops;
std::atomic<uint64_t> m_nextDeadlineCycle{0};
mutable std::mutex m_snapshotMutex;
EeKernelSnapshot m_snapshot;
uint64_t m_snapshotSequence = 0;
};