diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp index 3f20b5e1..a9facddf 100644 --- a/include/kernel/kernel.hpp +++ b/include/kernel/kernel.hpp @@ -58,6 +58,7 @@ class Kernel { // Top 8 bits are the major version, bottom 8 are the minor version u16 kernelVersion = 0; + u64 nextScheduledWakeupTick = std::numeric_limits::max(); // Shows whether a reschedule will be need bool needReschedule = false; @@ -94,6 +95,8 @@ public: void signalArbiter(u32 waitingAddress, s32 threadCount); void sleepThread(s64 ns); void sleepThreadOnArbiter(u32 waitingAddress); + void sleepThreadOnArbiterWithTimeout(u32 waitingAddress, s64 timeoutNs); + void switchThread(int newThreadIndex); void sortThreads(); std::optional getNextThread(); @@ -214,6 +217,8 @@ public: } } + void addWakeupEvent(u64 tick); + Handle makeObject(KernelObjectType type) { if (handleCounter > KernelHandles::Max) [[unlikely]] { Helpers::panic("Hlep we somehow created enough kernel objects to overflow this thing"); @@ -253,5 +258,7 @@ public: void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); } void clearInstructionCache(); void clearInstructionCacheRange(u32 start, u32 size); + void pollThreadWakeups(); + u32 getSharedFontVaddr(); }; \ No newline at end of file diff --git a/include/kernel/kernel_types.hpp b/include/kernel/kernel_types.hpp index a3a60c34..a5b27498 100644 --- a/include/kernel/kernel_types.hpp +++ b/include/kernel/kernel_types.hpp @@ -98,16 +98,17 @@ struct Session { }; enum class ThreadStatus { - Running, // Currently running - Ready, // Ready to run - WaitArbiter, // Waiting on an address arbiter - WaitSleep, // Waiting due to a SleepThread SVC - WaitSync1, // Waiting for the single object in the wait list to be ready - WaitSyncAny, // Wait for one object of the many that might be in the wait list to be ready - WaitSyncAll, // Waiting for ALL sync objects in its wait list to be ready - WaitIPC, // Waiting for the reply from an IPC request - Dormant, // Created but not yet made ready - Dead // Run to completion, or forcefully terminated + Running, // Currently running + Ready, // Ready to run + WaitArbiter, // Waiting on an address arbiter + WaitArbiterTimeout, // Waiting on an address arbiter with timeout + WaitSleep, // Waiting due to a SleepThread SVC + WaitSync1, // Waiting for the single object in the wait list to be ready + WaitSyncAny, // Wait for one object of the many that might be in the wait list to be ready + WaitSyncAll, // Waiting for ALL sync objects in its wait list to be ready + WaitIPC, // Waiting for the reply from an IPC request + Dormant, // Created but not yet made ready + Dead // Run to completion, or forcefully terminated }; struct Thread { diff --git a/include/scheduler.hpp b/include/scheduler.hpp index cfc4d5e8..b81b8807 100644 --- a/include/scheduler.hpp +++ b/include/scheduler.hpp @@ -11,8 +11,9 @@ struct Scheduler { VBlank = 0, // End of frame event UpdateTimers = 1, // Update kernel timer objects RunDSP = 2, // Make the emulated DSP run for one audio frame - SignalY2R = 3, // Signal that a Y2R conversion has finished - Panic = 4, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX) + ThreadWakeup = 3, // A thread is going to wake up and we need to reschedule threads + SignalY2R = 4, // Signal that a Y2R conversion has finished + Panic = 5, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX) TotalNumberOfEvents // How many event types do we have in total? }; static constexpr usize totalNumberOfEvents = static_cast(EventType::TotalNumberOfEvents); @@ -88,4 +89,4 @@ struct Scheduler { return (arm11Clock * s64(ns)) / 1000000000; } -}; \ No newline at end of file +}; diff --git a/src/core/kernel/address_arbiter.cpp b/src/core/kernel/address_arbiter.cpp index d15c81b8..ecb0a302 100644 --- a/src/core/kernel/address_arbiter.cpp +++ b/src/core/kernel/address_arbiter.cpp @@ -79,6 +79,14 @@ void Kernel::arbitrateAddress() { break; } + case ArbitrationType::WaitIfLessTimeout: { + s32 word = static_cast(mem.read32(address)); // Yes this is meant to be signed + if (word < value) { + sleepThreadOnArbiterWithTimeout(address, ns); + } + break; + } + case ArbitrationType::Signal: signalArbiter(address, value); break; @@ -98,8 +106,9 @@ void Kernel::signalArbiter(u32 waitingAddress, s32 threadCount) { // Wake threads with the highest priority threads being woken up first for (auto index : threadIndices) { Thread& t = threads[index]; - if (t.status == ThreadStatus::WaitArbiter && t.waitingAddress == waitingAddress) { + if ((t.status == ThreadStatus::WaitArbiter || t.status == ThreadStatus::WaitArbiterTimeout) && t.waitingAddress == waitingAddress) { t.status = ThreadStatus::Ready; + t.gprs[0] = Result::Success; // Return that the arbiter was actually signalled and that we didn't timeout count += 1; // Check if we've reached the max number of. If count < 0 then all threads are released. diff --git a/src/core/kernel/events.cpp b/src/core/kernel/events.cpp index 6d3dfbd7..c06204a9 100644 --- a/src/core/kernel/events.cpp +++ b/src/core/kernel/events.cpp @@ -137,6 +137,7 @@ void Kernel::waitSynchronization1() { // Add the current thread to the object's wait list object->getWaitlist() |= (1ull << currentThreadIndex); + addWakeupEvent(t.wakeupTick); requireReschedule(); } } @@ -231,6 +232,7 @@ void Kernel::waitSynchronizationN() { waitObjects[i].second->getWaitlist() |= (1ull << currentThreadIndex); // And add the thread to the object's waitlist } + addWakeupEvent(t.wakeupTick); requireReschedule(); } else { Helpers::panic("WaitSynchronizationN with waitAll"); diff --git a/src/core/kernel/kernel.cpp b/src/core/kernel/kernel.cpp index e097fab6..4e46fbb0 100644 --- a/src/core/kernel/kernel.cpp +++ b/src/core/kernel/kernel.cpp @@ -1,7 +1,10 @@ -#include #include "kernel.hpp" -#include "kernel_types.hpp" + +#include +#include + #include "cpu.hpp" +#include "kernel_types.hpp" Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config) : cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config) { @@ -159,6 +162,7 @@ void Kernel::reset() { threadIndices.clear(); serviceManager.reset(); + nextScheduledWakeupTick = std::numeric_limits::max(); needReschedule = false; // Allocate handle #0 to a dummy object and make a main process object diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp index 9eb7a197..108f6129 100644 --- a/src/core/kernel/threads.cpp +++ b/src/core/kernel/threads.cpp @@ -1,6 +1,8 @@ +#include #include #include #include +#include #include "arm_defs.hpp" #include "kernel.hpp" @@ -50,8 +52,7 @@ void Kernel::sortThreads() { bool Kernel::canThreadRun(const Thread& t) { if (t.status == ThreadStatus::Ready) { return true; - } else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 - || t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll) { + } else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll || t.status == ThreadStatus::WaitArbiterTimeout) { // TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All} return cpu.getTicks() >= t.wakeupTick; } @@ -216,6 +217,23 @@ void Kernel::sleepThreadOnArbiter(u32 waitingAddress) { requireReschedule(); } +void Kernel::sleepThreadOnArbiterWithTimeout(u32 waitingAddress, s64 timeoutNs) { + regs[0] = Result::OS::Timeout; // This will be overwritten with success if we don't timeout + + // Timeout is 0, don't bother waiting, instantly timeout + if (timeoutNs == 0) { + return; + } + + Thread& t = threads[currentThreadIndex]; + t.status = ThreadStatus::WaitArbiterTimeout; + t.waitingAddress = waitingAddress; + t.wakeupTick = getWakeupTick(timeoutNs); + + addWakeupEvent(t.wakeupTick); + requireReschedule(); +} + // Acquires an object that is **ready to be acquired** without waiting on it void Kernel::acquireSyncObject(KernelObject* object, const Thread& thread) { switch (object->type) { @@ -396,6 +414,7 @@ void Kernel::sleepThread(s64 ns) { t.status = ThreadStatus::WaitSleep; t.wakeupTick = getWakeupTick(ns); + addWakeupEvent(t.wakeupTick); requireReschedule(); } } @@ -697,3 +716,39 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) { return true; } } + +void Kernel::pollThreadWakeups() { + rescheduleThreads(); + bool haveSleepingThread = false; + u64 nextWakeupTick = std::numeric_limits::max(); + + for (auto index : threadIndices) { + const Thread& t = threads[index]; + + if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny || + t.status == ThreadStatus::WaitSyncAll || t.status == ThreadStatus::WaitArbiterTimeout) { + nextWakeupTick = std::min(nextWakeupTick, t.wakeupTick); + haveSleepingThread = true; + } + } + + auto& scheduler = cpu.getScheduler(); + + if (haveSleepingThread && nextWakeupTick > scheduler.currentTimestamp) { + nextScheduledWakeupTick = nextWakeupTick; + scheduler.addEvent(Scheduler::EventType::ThreadWakeup, nextWakeupTick); + } else { + nextScheduledWakeupTick = std::numeric_limits::max(); + } +} + +void Kernel::addWakeupEvent(u64 tick) { + // We only need to queue the event if the tick of the wakeup is coming sooner than our next scheduled wakeup. + if (nextScheduledWakeupTick > tick) { + nextScheduledWakeupTick = tick; + auto& scheduler = cpu.getScheduler(); + + scheduler.removeEvent(Scheduler::EventType::ThreadWakeup); + scheduler.addEvent(Scheduler::EventType::ThreadWakeup, tick); + } +} \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index 11970d91..ff91aa9e 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -177,6 +177,7 @@ void Emulator::pollScheduler() { break; } + case Scheduler::EventType::ThreadWakeup: kernel.pollThreadWakeups(); break; case Scheduler::EventType::UpdateTimers: kernel.pollTimers(); break; case Scheduler::EventType::RunDSP: { dsp->runAudioFrame(time);