diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp index 3d801c7a..3fd926e7 100644 --- a/include/kernel/kernel.hpp +++ b/include/kernel/kernel.hpp @@ -59,6 +59,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; @@ -95,6 +96,8 @@ class Kernel { 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(); @@ -215,6 +218,8 @@ class Kernel { } } + 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"); @@ -252,6 +257,8 @@ class Kernel { void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); } void clearInstructionCache(); void clearInstructionCacheRange(u32 start, u32 size); + void pollThreadWakeups(); + u32 getSharedFontVaddr(); // For debuggers: Returns information about the main process' (alive) threads in a vector for the frontend to display 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 8765ae66..9a51f893 100644 --- a/include/scheduler.hpp +++ b/include/scheduler.hpp @@ -9,11 +9,12 @@ struct Scheduler { enum class EventType { VBlank = 0, // End of frame event - UpdateTimers = 1, // Update kernel timer objects + ThreadWakeup = 1, // A thread might wake up from eg sleeping or timing outs RunDSP = 2, // Make the emulated DSP run for one audio frame - SignalY2R = 3, // Signal that a Y2R conversion has finished - UpdateIR = 4, // Update an IR device (For now, just the CirclePad Pro/N3DS controls) - Panic = 5, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX) + UpdateTimers = 3, // Update kernel timer objects + SignalY2R = 4, // Signal that a Y2R conversion has finished + UpdateIR = 5, // Update an IR device (For now, just the CirclePad Pro/N3DS controls) + Panic = 6, // 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); @@ -89,4 +90,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 4d3a6c2e..c4a51e7c 100644 --- a/src/core/kernel/address_arbiter.cpp +++ b/src/core/kernel/address_arbiter.cpp @@ -80,6 +80,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; default: Helpers::panic("ArbitrateAddress: Unimplemented type %s", arbitrationTypeToString(type)); } @@ -96,8 +104,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 2509ceb3..9b204f42 100644 --- a/src/core/kernel/events.cpp +++ b/src/core/kernel/events.cpp @@ -138,6 +138,7 @@ void Kernel::waitSynchronization1() { // Add the current thread to the object's wait list object->getWaitlist() |= (1ull << currentThreadIndex); + addWakeupEvent(t.wakeupTick); requireReschedule(); } } @@ -232,6 +233,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 c5dd3db2..824017d0 100644 --- a/src/core/kernel/kernel.cpp +++ b/src/core/kernel/kernel.cpp @@ -1,6 +1,7 @@ #include "kernel.hpp" #include +#include #include "cpu.hpp" #include "kernel_types.hpp" @@ -161,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 @@ -180,9 +182,7 @@ void Kernel::reset() { } // Get pointer to thread-local storage -u32 Kernel::getTLSPointer() { - return VirtualAddrs::TLSBase + currentThreadIndex * VirtualAddrs::TLSSize; -} +u32 Kernel::getTLSPointer() { return VirtualAddrs::TLSBase + currentThreadIndex * VirtualAddrs::TLSSize; } // Result CloseHandle(Handle handle) void Kernel::svcCloseHandle() { diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp index 4a933e6e..ce278713 100644 --- a/src/core/kernel/threads.cpp +++ b/src/core/kernel/threads.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "arm_defs.hpp" #include "kernel.hpp" @@ -50,7 +51,7 @@ 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) { + 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; } @@ -215,6 +216,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) { @@ -390,6 +408,7 @@ void Kernel::sleepThread(s64 ns) { t.status = ThreadStatus::WaitSleep; t.wakeupTick = getWakeupTick(ns); + addWakeupEvent(t.wakeupTick); requireReschedule(); } } @@ -688,6 +707,42 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) { } } +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); + } +} + std::vector Kernel::getMainProcessThreads() { // Sort the thread indices so that they appear nicer in the debugger auto indices = threadIndices; diff --git a/src/emulator.cpp b/src/emulator.cpp index 1079fc38..0f97208a 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -174,6 +174,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);