diff --git a/include/cpu_dynarmic.hpp b/include/cpu_dynarmic.hpp index 1ec50d9e..97acddd0 100644 --- a/include/cpu_dynarmic.hpp +++ b/include/cpu_dynarmic.hpp @@ -126,7 +126,7 @@ class CPU { Emulator& emu; public: - static constexpr u64 ticksPerSec = 268111856; + static constexpr u64 ticksPerSec = Scheduler::arm11Clock; CPU(Memory& mem, Kernel& kernel, Emulator& emu); void reset(); @@ -175,6 +175,10 @@ class CPU { return scheduler.currentTimestamp; } + Scheduler& getScheduler() { + return scheduler; + } + 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 3f09bf12..217dd148 100644 --- a/include/kernel/kernel.hpp +++ b/include/kernel/kernel.hpp @@ -70,6 +70,7 @@ public: Handle makeMutex(bool locked = false); // Needs to be public to be accessible to the APT/DSP services Handle makeSemaphore(u32 initialCount, u32 maximumCount); // Needs to be public to be accessible to the service manager port Handle makeTimer(ResetType resetType); + void pollTimers(); // Signals an event, returns true on success or false if the event does not exist bool signalEvent(Handle e); @@ -94,7 +95,6 @@ public: void releaseMutex(Mutex* moo); void cancelTimer(Timer* timer); void signalTimer(Handle timerHandle, Timer* timer); - void updateTimer(Handle timerHandle, Timer* timer); // 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 53c60774..79684e17 100644 --- a/include/kernel/kernel_types.hpp +++ b/include/kernel/kernel_types.hpp @@ -177,13 +177,12 @@ struct Timer { u64 waitlist; // Refer to the getWaitlist function below for documentation ResetType resetType = ResetType::OneShot; - u64 startTick; // CPU tick the timer started - u64 currentDelay; // Number of ns until the timer fires next time + u64 fireTick; // CPU tick the timer will be fired u64 interval; // Number of ns until the timer fires for the second and future times bool fired; // Has this timer been signalled? bool running; // Is this timer running or stopped? - Timer(ResetType type) : resetType(type), startTick(0), currentDelay(0), interval(0), waitlist(0), fired(false), running(false) {} + Timer(ResetType type) : resetType(type), fireTick(0), interval(0), waitlist(0), fired(false), running(false) {} }; struct MemoryBlock { diff --git a/include/scheduler.hpp b/include/scheduler.hpp index fb097618..69c91f06 100644 --- a/include/scheduler.hpp +++ b/include/scheduler.hpp @@ -5,14 +5,17 @@ #include <ranges> #include "helpers.hpp" +#include "logger.hpp" struct Scheduler { enum class EventType { VBlank = 0, // End of frame event - Panic = 1, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX) + UpdateTimers = 1, // Update kernel timer objects + Panic = 2, // 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<usize>(EventType::TotalNumberOfEvents); + static constexpr u64 arm11Clock = 268111856; template <typename Key, typename Val, usize size> using EventMap = boost::container::flat_multimap<Key, Val, std::less<Key>, boost::container::static_vector<std::pair<Key, Val>, size>>; @@ -46,4 +49,37 @@ struct Scheduler { // Add a dummy event to always keep the scheduler non-empty addEvent(EventType::Panic, std::numeric_limits<u64>::max()); } + + private: + static constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / arm11Clock; + + public: + // Function for converting time units to cycles for various kernel functions + // Thank you Citra + static constexpr s64 nsToCycles(float ns) { return s64(arm11Clock * (0.000000001f) * ns); } + static constexpr s64 nsToCycles(int ns) { return arm11Clock * s64(ns) / 1000000000; } + + static constexpr s64 nsToCycles(s64 ns) { + if (ns / 1000000000 > static_cast<s64>(MAX_VALUE_TO_MULTIPLY)) { + return std::numeric_limits<s64>::max(); + } + + if (ns > static_cast<s64>(MAX_VALUE_TO_MULTIPLY)) { + return arm11Clock * (ns / 1000000000); + } + + return (arm11Clock * ns) / 1000000000; + } + + static constexpr s64 nsToCycles(u64 ns) { + if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) { + return std::numeric_limits<s64>::max(); + } + + if (ns > MAX_VALUE_TO_MULTIPLY) { + return arm11Clock * (s64(ns) / 1000000000); + } + + return (arm11Clock * s64(ns)) / 1000000000; + } }; \ No newline at end of file diff --git a/src/core/CPU/cpu_dynarmic.cpp b/src/core/CPU/cpu_dynarmic.cpp index f373dc06..94ce335e 100644 --- a/src/core/CPU/cpu_dynarmic.cpp +++ b/src/core/CPU/cpu_dynarmic.cpp @@ -36,7 +36,7 @@ void CPU::runFrame() { while (!emu.frameDone) { // Run CPU until the next scheduler event - env.ticksLeft = scheduler.nextTimestamp; + env.ticksLeft = scheduler.nextTimestamp - scheduler.currentTimestamp; execute: const auto exitReason = jit->Run(); @@ -54,6 +54,8 @@ void CPU::runFrame() { } } } + + printf("CPU END!\n"); } #endif // CPU_DYNARMIC \ No newline at end of file diff --git a/src/core/kernel/timers.cpp b/src/core/kernel/timers.cpp index a9c95292..f245f9dc 100644 --- a/src/core/kernel/timers.cpp +++ b/src/core/kernel/timers.cpp @@ -1,5 +1,8 @@ -#include "kernel.hpp" +#include <limits> + #include "cpu.hpp" +#include "kernel.hpp" +#include "scheduler.hpp" Handle Kernel::makeTimer(ResetType type) { Handle ret = makeObject(KernelObjectType::Timer); @@ -13,30 +16,48 @@ Handle Kernel::makeTimer(ResetType type) { return ret; } -void Kernel::updateTimer(Handle handle, Timer* timer) { - if (timer->running) { - const u64 currentTicks = cpu.getTicks(); - u64 elapsedTicks = currentTicks - timer->startTick; +void Kernel::pollTimers() { + u64 currentTick = cpu.getTicks(); - constexpr double ticksPerSec = double(CPU::ticksPerSec); - constexpr double nsPerTick = ticksPerSec / 1000000000.0; - const s64 elapsedNs = s64(double(elapsedTicks) * nsPerTick); + // Find the next timestamp we'll poll KTimers on. To do this, we find the minimum tick one of our timers will fire + u64 nextTimestamp = std::numeric_limits<u64>::max(); + // Do we have any active timers anymore? If not, then we won't need to schedule a new timer poll event + bool haveActiveTimers = false; - // Timer has fired - if (elapsedNs >= timer->currentDelay) { - timer->startTick = currentTicks; - timer->currentDelay = timer->interval; - signalTimer(handle, timer); + for (auto handle : timerHandles) { + KernelObject* object = getObject(handle, KernelObjectType::Timer); + if (object != nullptr) { + Timer* timer = object->getData<Timer>(); + + if (timer->running) { + // If timer has fired, signal it and set the tick it will next time + if (currentTick >= timer->fireTick) { + signalTimer(handle, timer); + } + + // Update our next timer fire timestamp and mark that we should schedule a new event to poll timers + // We recheck timer->running because signalling a timer stops it if interval == 0 + if (timer->running) { + nextTimestamp = std::min<u64>(nextTimestamp, timer->fireTick); + haveActiveTimers = true; + } + } } } + + // If we still have active timers, schedule next poll event + if (haveActiveTimers) { + Scheduler& scheduler = cpu.getScheduler(); + scheduler.addEvent(Scheduler::EventType::UpdateTimers, nextTimestamp); + } } void Kernel::cancelTimer(Timer* timer) { timer->running = false; - // TODO: When we have a scheduler this should properly cancel timer events in the scheduler } void Kernel::signalTimer(Handle timerHandle, Timer* timer) { + printf("DEEPFRIED\n"); timer->fired = true; requireReschedule(); @@ -54,6 +75,8 @@ void Kernel::signalTimer(Handle timerHandle, Timer* timer) { if (timer->interval == 0) { cancelTimer(timer); + } else { + timer->fireTick = cpu.getTicks() + Scheduler::nsToCycles(timer->interval); } } @@ -87,18 +110,20 @@ void Kernel::svcSetTimer() { Timer* timer = object->getData<Timer>(); cancelTimer(timer); - timer->currentDelay = initial; timer->interval = interval; timer->running = true; - timer->startTick = cpu.getTicks(); + timer->fireTick = cpu.getTicks() + Scheduler::nsToCycles(initial); + + Scheduler& scheduler = cpu.getScheduler(); + // Signal an event to poll timers as soon as possible + scheduler.removeEvent(Scheduler::EventType::UpdateTimers); + scheduler.addEvent(Scheduler::EventType::UpdateTimers, cpu.getTicks() + 1); // If the initial delay is 0 then instantly signal the timer if (initial == 0) { signalTimer(handle, timer); - } else { - // This should schedule an event in the scheduler when we have one } - + regs[0] = Result::Success; } diff --git a/src/emulator.cpp b/src/emulator.cpp index 46efd209..058fe868 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -120,7 +120,7 @@ void Emulator::pollScheduler() { auto& events = scheduler.events; // Pop events until there's none pending anymore - while (scheduler.currentTimestamp > scheduler.nextTimestamp) { + while (scheduler.currentTimestamp >= scheduler.nextTimestamp) { // Read event timestamp and type, pop it from the scheduler and handle it auto [time, eventType] = std::move(*events.begin()); events.erase(events.begin()); @@ -129,6 +129,7 @@ void Emulator::pollScheduler() { switch (eventType) { case Scheduler::EventType::VBlank: { + printf("VBLANK!!!!!!\n"); // Signal that we've reached the end of a frame frameDone = true; lua.signalEvent(LuaEvent::Frame); @@ -143,6 +144,8 @@ void Emulator::pollScheduler() { break; } + case Scheduler::EventType::UpdateTimers: kernel.pollTimers(); break; + default: { Helpers::panic("Scheduler: Unimplemented event type received: %d\n", static_cast<int>(eventType)); break;