diff --git a/include/cpu_dynarmic.hpp b/include/cpu_dynarmic.hpp index 97acddd0..048bc62a 100644 --- a/include/cpu_dynarmic.hpp +++ b/include/cpu_dynarmic.hpp @@ -179,6 +179,8 @@ class CPU { return scheduler; } + void addTicks(u64 ticks) { env.AddTicks(ticks); } + void clearCache() { jit->ClearCache(); } void runFrame(); }; \ No newline at end of file diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp index d35d9031..e78a588a 100644 --- a/include/kernel/kernel.hpp +++ b/include/kernel/kernel.hpp @@ -95,6 +95,7 @@ public: void releaseMutex(Mutex* moo); void cancelTimer(Timer* timer); void signalTimer(Handle timerHandle, Timer* timer); + u64 getWakeupTick(s64 ns); // Wake up the thread with the highest priority out of all threads in the waitlist // Returns the index of the woken up thread diff --git a/include/kernel/kernel_types.hpp b/include/kernel/kernel_types.hpp index 79684e17..01af4bd9 100644 --- a/include/kernel/kernel_types.hpp +++ b/include/kernel/kernel_types.hpp @@ -83,56 +83,53 @@ struct Port { }; struct Session { - Handle portHandle; // The port this session is subscribed to - Session(Handle portHandle) : portHandle(portHandle) {} + Handle portHandle; // The port this session is subscribed to + Session(Handle portHandle) : portHandle(portHandle) {} }; 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 + 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 { - u32 initialSP; // Initial r13 value - u32 entrypoint; // Initial r15 value - u32 priority; - u32 arg; - ProcessorID processorID; - ThreadStatus status; - Handle handle; // OS handle for this thread - int index; // Index of the thread. 0 for the first thread, 1 for the second, and so on + u32 initialSP; // Initial r13 value + u32 entrypoint; // Initial r15 value + u32 priority; + u32 arg; + ProcessorID processorID; + ThreadStatus status; + Handle handle; // OS handle for this thread + int index; // Index of the thread. 0 for the first thread, 1 for the second, and so on - // The waiting address for threads that are waiting on an AddressArbiter - u32 waitingAddress; + // The waiting address for threads that are waiting on an AddressArbiter + u32 waitingAddress; - // The nanoseconds until a thread wakes up from being asleep or from timing out while waiting on an arbiter - u64 waitingNanoseconds; - // The tick this thread went to sleep on - u64 sleepTick; - // For WaitSynchronization(N): A vector of objects this thread is waiting for - std::vector waitList; - // For WaitSynchronizationN: Shows whether the object should wait for all objects in the wait list or just one - bool waitAll; - // For WaitSynchronizationN: The "out" pointer - u32 outPointer; + // For WaitSynchronization(N): A vector of objects this thread is waiting for + std::vector waitList; + // For WaitSynchronizationN: Shows whether the object should wait for all objects in the wait list or just one + bool waitAll; + // For WaitSynchronizationN: The "out" pointer + u32 outPointer; + u64 wakeupTick; - // Thread context used for switching between threads - std::array gprs; - std::array fprs; // Stored as u32 because dynarmic does it - u32 cpsr; - u32 fpscr; - u32 tlsBase; // Base pointer for thread-local storage + // Thread context used for switching between threads + std::array gprs; + std::array fprs; // Stored as u32 because dynarmic does it + u32 cpsr; + u32 fpscr; + u32 tlsBase; // Base pointer for thread-local storage - // A list of threads waiting for this thread to terminate. Yes, threads are sync objects too. - u64 threadsWaitingForTermination; + // A list of threads waiting for this thread to terminate. Yes, threads are sync objects too. + u64 threadsWaitingForTermination; }; static const char* kernelObjectTypeToString(KernelObjectType t) { diff --git a/src/core/kernel/events.cpp b/src/core/kernel/events.cpp index 7da4788e..b2f89fbf 100644 --- a/src/core/kernel/events.cpp +++ b/src/core/kernel/events.cpp @@ -126,8 +126,7 @@ void Kernel::waitSynchronization1() { auto& t = threads[currentThreadIndex]; t.waitList.resize(1); t.status = ThreadStatus::WaitSync1; - t.sleepTick = cpu.getTicks(); - t.waitingNanoseconds = ns; + t.wakeupTick = getWakeupTick(ns); t.waitList[0] = handle; // Add the current thread to the object's wait list @@ -220,8 +219,7 @@ void Kernel::waitSynchronizationN() { t.waitList.resize(handleCount); t.status = ThreadStatus::WaitSyncAny; t.outPointer = outPointer; - t.waitingNanoseconds = ns; - t.sleepTick = cpu.getTicks(); + t.wakeupTick = getWakeupTick(ns); for (s32 i = 0; i < handleCount; i++) { t.waitList[i] = waitObjects[i].first; // Add object to this thread's waitlist diff --git a/src/core/kernel/idle_thread.cpp b/src/core/kernel/idle_thread.cpp index 5abba373..d666968b 100644 --- a/src/core/kernel/idle_thread.cpp +++ b/src/core/kernel/idle_thread.cpp @@ -8,13 +8,6 @@ The code for our idle thread looks like this idle_thread_main: - mov r0, #4096 @ Loop counter - - .loop: - nop; nop; nop; nop @ NOP 4 times to waste some cycles - subs r0, #1 @ Decrement counter by 1, go back to looping if loop counter != 0 - bne .loop - // Sleep for 0 seconds with the SleepThread SVC, which just yields execution mov r0, #0 mov r1, #0 @@ -24,14 +17,10 @@ idle_thread_main: */ static constexpr u8 idleThreadCode[] = { - 0x01, 0x0A, 0xA0, 0xE3, // mov r0, #4096 - 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, // nop (4 times) - 0x01, 0x00, 0x50, 0xE2, // subs r0, #1 - 0xF9, 0xFF, 0xFF, 0x1A, // bne loop 0x00, 0x00, 0xA0, 0xE3, // mov r0, #0 0x00, 0x10, 0xA0, 0xE3, // mov r1, #0 0x0A, 0x00, 0x00, 0xEF, // svc SleepThread - 0xF4, 0xFF, 0xFF, 0xEA // b idle_thread_main + 0xFB, 0xFF, 0xFF, 0xEA // b idle_thread_main }; // Set up an idle thread to run when no thread is able to run diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp index 239a6617..3a6201c1 100644 --- a/src/core/kernel/threads.cpp +++ b/src/core/kernel/threads.cpp @@ -52,14 +52,8 @@ bool Kernel::canThreadRun(const Thread& t) { return true; } else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll) { - const u64 elapsedTicks = cpu.getTicks() - t.sleepTick; - - constexpr double ticksPerSec = double(CPU::ticksPerSec); - constexpr double nsPerTick = ticksPerSec / 1000000000.0; - // TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All} - const s64 elapsedNs = s64(double(elapsedTicks) * nsPerTick); - return elapsedNs >= t.waitingNanoseconds; + return cpu.getTicks() >= t.wakeupTick; } // Handle timeouts and stuff here @@ -82,6 +76,15 @@ std::optional Kernel::getNextThread() { return std::nullopt; } +u64 Kernel::getWakeupTick(s64 ns) { + // Timeout == -1 means that the thread doesn't plan on waking up automatically + if (ns == -1) { + return std::numeric_limits::max(); + } + + return cpu.getTicks() + Scheduler::nsToCycles(ns); +} + // See if there is a higher priority, ready thread and switch to that void Kernel::rescheduleThreads() { Thread& current = threads[currentThreadIndex]; // Current running thread @@ -368,13 +371,30 @@ void Kernel::sleepThread(s64 ns) { if (index != idleThreadIndex) { switchThread(index); } + } else { + if (currentThreadIndex == idleThreadIndex) { + const Scheduler& scheduler = cpu.getScheduler(); + u64 timestamp = scheduler.nextTimestamp; + + for (auto i : threadIndices) { + const Thread& t = threads[i]; + if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny || + t.status == ThreadStatus::WaitSyncAll) { + timestamp = std::min(timestamp, t.wakeupTick); + } + } + + if (timestamp > scheduler.currentTimestamp) { + u64 idleCycles = timestamp - scheduler.currentTimestamp; + cpu.addTicks(idleCycles); + } + } } } else { // If we're sleeping for >= 0 ns Thread& t = threads[currentThreadIndex]; t.status = ThreadStatus::WaitSleep; - t.waitingNanoseconds = ns; - t.sleepTick = cpu.getTicks(); + t.wakeupTick = getWakeupTick(ns); requireReschedule(); }