From 7343497f36820ac914f57c60fb98fcb19c93fa88 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 20 Jan 2024 23:17:35 +0200 Subject: [PATCH 1/7] Add basic scheduler structure --- CMakeLists.txt | 2 +- include/cpu_dynarmic.hpp | 16 ++++++----- include/scheduler.hpp | 50 +++++++++++++++++++++++++++++++++++ src/core/CPU/cpu_dynarmic.cpp | 45 +++++++++++++++++-------------- 4 files changed, 85 insertions(+), 28 deletions(-) create mode 100644 include/scheduler.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b4b1503a..e557c5c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -244,7 +244,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/services/news_u.hpp include/applets/software_keyboard.hpp include/applets/applet_manager.hpp include/fs/archive_user_save_data.hpp include/services/amiibo_device.hpp include/services/nfc_types.hpp include/swap.hpp include/services/csnd.hpp include/services/nwm_uds.hpp include/fs/archive_system_save_data.hpp include/lua_manager.hpp include/memory_mapped_file.hpp include/hydra_icon.hpp - include/PICA/dynapica/shader_rec_emitter_arm64.hpp + include/PICA/dynapica/shader_rec_emitter_arm64.hpp include/scheduler.hpp ) cmrc_add_resource_library( diff --git a/include/cpu_dynarmic.hpp b/include/cpu_dynarmic.hpp index 8f1e277b..e45705de 100644 --- a/include/cpu_dynarmic.hpp +++ b/include/cpu_dynarmic.hpp @@ -9,6 +9,7 @@ #include "helpers.hpp" #include "kernel.hpp" #include "memory.hpp" +#include "scheduler.hpp" class CPU; @@ -112,15 +113,16 @@ public: }; class CPU { - std::unique_ptr jit; - std::shared_ptr cp15; + std::unique_ptr jit; + std::shared_ptr cp15; - // Make exclusive monitor with only 1 CPU core - Dynarmic::ExclusiveMonitor exclusiveMonitor{1}; - MyEnvironment env; - Memory& mem; + // Make exclusive monitor with only 1 CPU core + Dynarmic::ExclusiveMonitor exclusiveMonitor{1}; + MyEnvironment env; + Memory& mem; + Scheduler scheduler; -public: + public: static constexpr u64 ticksPerSec = 268111856; CPU(Memory& mem, Kernel& kernel); diff --git a/include/scheduler.hpp b/include/scheduler.hpp new file mode 100644 index 00000000..e941259c --- /dev/null +++ b/include/scheduler.hpp @@ -0,0 +1,50 @@ +#pragma once +#include +#include +#include +#include + +#include "helpers.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) + TotalNumberOfEvents // How many event types do we have in total? + }; + static constexpr usize totalNumberOfEvents = static_cast(EventType::TotalNumberOfEvents); + + template + using EventMap = boost::container::flat_multimap, boost::container::static_vector, size>>; + + EventMap events; + u64 currentTimestamp = 0; + u64 nextTimestamp = 0; + + // Set nextTimestamp to the timestamp of the next event + void updateNextTimestamp() { nextTimestamp = events.cbegin()->first; } + void addCycles(u64 cycles) { currentTimestamp += cycles; } + + void addEvent(EventType type, u64 timestamp) { + events.emplace(timestamp, type); + updateNextTimestamp(); + } + + void removeEvent(EventType type) { + auto it = std::ranges::find_if(events, [type](decltype(events)::const_reference pair) { return pair.second == type; }); + + if (it != events.end()) { + events.erase(it); + updateNextTimestamp(); + } + }; + + void reset() { + currentTimestamp = 0; + + // Clear any pending events + events.clear(); + // Add a dummy event to always keep the scheduler non-empty + addEvent(EventType::Panic, std::numeric_limits::max()); + } +}; \ No newline at end of file diff --git a/src/core/CPU/cpu_dynarmic.cpp b/src/core/CPU/cpu_dynarmic.cpp index 29ca49d1..62e484be 100644 --- a/src/core/CPU/cpu_dynarmic.cpp +++ b/src/core/CPU/cpu_dynarmic.cpp @@ -1,32 +1,37 @@ #ifdef CPU_DYNARMIC #include "cpu_dynarmic.hpp" + #include "arm_defs.hpp" CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel) { - cp15 = std::make_shared(); + cp15 = std::make_shared(); - Dynarmic::A32::UserConfig config; - config.arch_version = Dynarmic::A32::ArchVersion::v6K; - config.callbacks = &env; - config.coprocessors[15] = cp15; - config.define_unpredictable_behaviour = true; - config.global_monitor = &exclusiveMonitor; - config.processor_id = 0; - - jit = std::make_unique(config); + Dynarmic::A32::UserConfig config; + config.arch_version = Dynarmic::A32::ArchVersion::v6K; + config.callbacks = &env; + config.coprocessors[15] = cp15; + config.define_unpredictable_behaviour = true; + config.global_monitor = &exclusiveMonitor; + config.processor_id = 0; + + jit = std::make_unique(config); } void CPU::reset() { - setCPSR(CPSR::UserMode); - setFPSCR(FPSCR::MainThreadDefault); - env.totalTicks = 0; + setCPSR(CPSR::UserMode); + setFPSCR(FPSCR::MainThreadDefault); + env.totalTicks = 0; - cp15->reset(); - cp15->setTLSBase(VirtualAddrs::TLSBase); // Set cp15 TLS pointer to the main thread's thread-local storage - jit->Reset(); - jit->ClearCache(); - jit->Regs().fill(0); - jit->ExtRegs().fill(0); + cp15->reset(); + cp15->setTLSBase(VirtualAddrs::TLSBase); // Set cp15 TLS pointer to the main thread's thread-local storage + jit->Reset(); + jit->ClearCache(); + jit->Regs().fill(0); + jit->ExtRegs().fill(0); + + // Reset scheduler and add a VBlank event + scheduler.reset(); + scheduler.addEvent(Scheduler::EventType::VBlank, ticksPerSec / 60); } -#endif // CPU_DYNARMIC \ No newline at end of file +#endif // CPU_DYNARMIC \ No newline at end of file From 97f97dc6e27d5e0ec673d7bad624787510a865bd Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 22 Jan 2024 00:38:11 +0200 Subject: [PATCH 2/7] Use CPU for counting ticks instead of scheduler --- include/cpu_dynarmic.hpp | 64 ++++++++++++++++++----------------- include/scheduler.hpp | 4 --- src/core/CPU/cpu_dynarmic.cpp | 4 ++- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/include/cpu_dynarmic.hpp b/include/cpu_dynarmic.hpp index e45705de..a3fb3378 100644 --- a/include/cpu_dynarmic.hpp +++ b/include/cpu_dynarmic.hpp @@ -11,14 +11,16 @@ #include "memory.hpp" #include "scheduler.hpp" +class Emulator; class CPU; class MyEnvironment final : public Dynarmic::A32::UserCallbacks { -public: - u64 ticksLeft = 0; - u64 totalTicks = 0; - Memory& mem; - Kernel& kernel; + public: + u64 ticksLeft = 0; + u64 totalTicks = 0; + Memory& mem; + Kernel& kernel; + Scheduler& scheduler; u64 getCyclesForInstruction(bool isThumb, u32 instruction); @@ -77,39 +79,39 @@ public: std::terminate(); } - void CallSVC(u32 swi) override { - kernel.serviceSVC(swi); - } + void CallSVC(u32 swi) override { + kernel.serviceSVC(swi); + } - void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { - switch (exception) { - case Dynarmic::A32::Exception::UnpredictableInstruction: - Helpers::panic("Unpredictable instruction at pc = %08X", pc); - break; + void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { + switch (exception) { + case Dynarmic::A32::Exception::UnpredictableInstruction: + Helpers::panic("Unpredictable instruction at pc = %08X", pc); + break; - default: Helpers::panic("Fired exception oops"); - } - } + default: Helpers::panic("Fired exception oops"); + } + } - void AddTicks(u64 ticks) override { - totalTicks += ticks; + void AddTicks(u64 ticks) override { + totalTicks += ticks; - if (ticks > ticksLeft) { - ticksLeft = 0; - return; - } - ticksLeft -= ticks; - } + if (ticks > ticksLeft) { + ticksLeft = 0; + return; + } + ticksLeft -= ticks; + } - u64 GetTicksRemaining() override { - return ticksLeft; - } + u64 GetTicksRemaining() override { + return ticksLeft; + } - u64 GetTicksForCode(bool isThumb, u32 vaddr, u32 instruction) override { - return getCyclesForInstruction(isThumb, instruction); - } + u64 GetTicksForCode(bool isThumb, u32 vaddr, u32 instruction) override { + return getCyclesForInstruction(isThumb, instruction); + } - MyEnvironment(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {} + MyEnvironment(Memory& mem, Kernel& kernel, Scheduler& scheduler) : mem(mem), kernel(kernel), scheduler(scheduler) {} }; class CPU { diff --git a/include/scheduler.hpp b/include/scheduler.hpp index e941259c..3d2f582c 100644 --- a/include/scheduler.hpp +++ b/include/scheduler.hpp @@ -18,12 +18,10 @@ struct Scheduler { using EventMap = boost::container::flat_multimap, boost::container::static_vector, size>>; EventMap events; - u64 currentTimestamp = 0; u64 nextTimestamp = 0; // Set nextTimestamp to the timestamp of the next event void updateNextTimestamp() { nextTimestamp = events.cbegin()->first; } - void addCycles(u64 cycles) { currentTimestamp += cycles; } void addEvent(EventType type, u64 timestamp) { events.emplace(timestamp, type); @@ -40,8 +38,6 @@ struct Scheduler { }; void reset() { - currentTimestamp = 0; - // Clear any pending events events.clear(); // Add a dummy event to always keep the scheduler non-empty diff --git a/src/core/CPU/cpu_dynarmic.cpp b/src/core/CPU/cpu_dynarmic.cpp index 62e484be..caf8a66e 100644 --- a/src/core/CPU/cpu_dynarmic.cpp +++ b/src/core/CPU/cpu_dynarmic.cpp @@ -3,7 +3,7 @@ #include "arm_defs.hpp" -CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel) { +CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel, scheduler) { cp15 = std::make_shared(); Dynarmic::A32::UserConfig config; @@ -32,6 +32,8 @@ void CPU::reset() { // Reset scheduler and add a VBlank event scheduler.reset(); scheduler.addEvent(Scheduler::EventType::VBlank, ticksPerSec / 60); + + printf("%lld\n", scheduler.nextTimestamp); } #endif // CPU_DYNARMIC \ No newline at end of file From fa82dad38d412b7710ea3d9187cf267555b85171 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 22 Jan 2024 02:29:14 +0200 Subject: [PATCH 3/7] First stuff running with scheduler --- include/cpu_dynarmic.hpp | 30 ++++++-------------- include/emulator.hpp | 7 +++++ include/scheduler.hpp | 3 ++ src/core/CPU/cpu_dynarmic.cpp | 30 ++++++++++++++++---- src/emulator.cpp | 52 +++++++++++++++++++++++++++++------ 5 files changed, 86 insertions(+), 36 deletions(-) diff --git a/include/cpu_dynarmic.hpp b/include/cpu_dynarmic.hpp index a3fb3378..1ec50d9e 100644 --- a/include/cpu_dynarmic.hpp +++ b/include/cpu_dynarmic.hpp @@ -94,7 +94,7 @@ class MyEnvironment final : public Dynarmic::A32::UserCallbacks { } void AddTicks(u64 ticks) override { - totalTicks += ticks; + scheduler.currentTimestamp += ticks; if (ticks > ticksLeft) { ticksLeft = 0; @@ -111,7 +111,7 @@ class MyEnvironment final : public Dynarmic::A32::UserCallbacks { return getCyclesForInstruction(isThumb, instruction); } - MyEnvironment(Memory& mem, Kernel& kernel, Scheduler& scheduler) : mem(mem), kernel(kernel), scheduler(scheduler) {} + MyEnvironment(Memory& mem, Kernel& kernel, Scheduler& scheduler) : mem(mem), kernel(kernel), scheduler(scheduler) {} }; class CPU { @@ -122,12 +122,13 @@ class CPU { Dynarmic::ExclusiveMonitor exclusiveMonitor{1}; MyEnvironment env; Memory& mem; - Scheduler scheduler; + Scheduler& scheduler; + Emulator& emu; public: static constexpr u64 ticksPerSec = 268111856; - CPU(Memory& mem, Kernel& kernel); + CPU(Memory& mem, Kernel& kernel, Emulator& emu); void reset(); void setReg(int index, u32 value) { @@ -166,29 +167,14 @@ class CPU { } u64 getTicks() { - return env.totalTicks; + return scheduler.currentTimestamp; } // Get reference to tick count. Memory needs access to this u64& getTicksRef() { - return env.totalTicks; + return scheduler.currentTimestamp; } void clearCache() { jit->ClearCache(); } - - void runFrame() { - env.ticksLeft = ticksPerSec / 60; - execute: - const auto exitReason = jit->Run(); - - if (static_cast(exitReason) != 0) [[unlikely]] { - // Cache invalidation needs to exit the JIT so it returns a CacheInvalidation HaltReason. In our case, we just go back to executing - // The goto might be terrible but it does guarantee that this does not recursively call run and crash, instead getting optimized to a jump - if (Dynarmic::Has(exitReason, Dynarmic::HaltReason::CacheInvalidation)) { - goto execute; - } else { - Helpers::panic("Exit reason: %d\nPC: %08X", static_cast(exitReason), getReg(15)); - } - } - } + void runFrame(); }; \ No newline at end of file diff --git a/include/emulator.hpp b/include/emulator.hpp index f4537425..55ed8499 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -15,6 +15,7 @@ #include "io_file.hpp" #include "lua_manager.hpp" #include "memory.hpp" +#include "scheduler.hpp" #ifdef PANDA3DS_ENABLE_HTTP_SERVER #include "http_server.hpp" @@ -42,6 +43,7 @@ class Emulator { Kernel kernel; Crypto::AESEngine aesEngine; Cheats cheats; + Scheduler scheduler; // Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard // This is done so when a gamepad is connected, we won't automatically override the 3DS analog stick settings with the gamepad's state @@ -85,6 +87,8 @@ class Emulator { // change ROMs. If Reload is selected, the emulator will reload its selected ROM. This is useful for eg a "reset" button that keeps the current // ROM and just resets the emu enum class ReloadOption { NoReload, Reload }; + // Used in CPU::runFrame + bool frameDone = false; Emulator(); ~Emulator(); @@ -94,6 +98,8 @@ class Emulator { void reset(ReloadOption reload); void run(void* frontend = nullptr); void runFrame(); + // Poll the scheduler for events + void pollScheduler(); void resume(); // Resume the emulator void pause(); // Pause the emulator @@ -121,6 +127,7 @@ class Emulator { Cheats& getCheats() { return cheats; } ServiceManager& getServiceManager() { return kernel.getServiceManager(); } LuaManager& getLua() { return lua; } + Scheduler& getScheduler() { return scheduler; } RendererType getRendererType() const { return config.rendererType; } Renderer* getRenderer() { return gpu.getRenderer(); } diff --git a/include/scheduler.hpp b/include/scheduler.hpp index 3d2f582c..fb097618 100644 --- a/include/scheduler.hpp +++ b/include/scheduler.hpp @@ -18,6 +18,7 @@ struct Scheduler { using EventMap = boost::container::flat_multimap, boost::container::static_vector, size>>; EventMap events; + u64 currentTimestamp = 0; u64 nextTimestamp = 0; // Set nextTimestamp to the timestamp of the next event @@ -38,6 +39,8 @@ struct Scheduler { }; void reset() { + currentTimestamp = 0; + // Clear any pending events events.clear(); // Add a dummy event to always keep the scheduler non-empty diff --git a/src/core/CPU/cpu_dynarmic.cpp b/src/core/CPU/cpu_dynarmic.cpp index caf8a66e..f373dc06 100644 --- a/src/core/CPU/cpu_dynarmic.cpp +++ b/src/core/CPU/cpu_dynarmic.cpp @@ -2,8 +2,9 @@ #include "cpu_dynarmic.hpp" #include "arm_defs.hpp" +#include "emulator.hpp" -CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel, scheduler) { +CPU::CPU(Memory& mem, Kernel& kernel, Emulator& emu) : mem(mem), emu(emu), scheduler(emu.getScheduler()), env(mem, kernel, emu.getScheduler()) { cp15 = std::make_shared(); Dynarmic::A32::UserConfig config; @@ -28,12 +29,31 @@ void CPU::reset() { jit->ClearCache(); jit->Regs().fill(0); jit->ExtRegs().fill(0); +} - // Reset scheduler and add a VBlank event - scheduler.reset(); - scheduler.addEvent(Scheduler::EventType::VBlank, ticksPerSec / 60); +void CPU::runFrame() { + emu.frameDone = false; - printf("%lld\n", scheduler.nextTimestamp); + while (!emu.frameDone) { + // Run CPU until the next scheduler event + env.ticksLeft = scheduler.nextTimestamp; + + execute: + const auto exitReason = jit->Run(); + + // Handle any scheduler events that need handling. + emu.pollScheduler(); + + if (static_cast(exitReason) != 0) [[unlikely]] { + // Cache invalidation needs to exit the JIT so it returns a CacheInvalidation HaltReason. In our case, we just go back to executing + // The goto might be terrible but it does guarantee that this does not recursively call run and crash, instead getting optimized to a jump + if (Dynarmic::Has(exitReason, Dynarmic::HaltReason::CacheInvalidation)) { + goto execute; + } else { + Helpers::panic("Exit reason: %d\nPC: %08X", static_cast(exitReason), getReg(15)); + } + } + } } #endif // CPU_DYNARMIC \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index e94170a2..46efd209 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -17,10 +17,11 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; #endif Emulator::Emulator() - : config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel), gpu(memory, config), - memory(cpu.getTicksRef(), config), cheats(memory, kernel.getServiceManager().getHID()), lua(memory), running(false), programRunning(false) + : config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel, *this), gpu(memory, config), memory(cpu.getTicksRef(), config), + cheats(memory, kernel.getServiceManager().getHID()), lua(memory), running(false), programRunning(false) #ifdef PANDA3DS_ENABLE_HTTP_SERVER - , httpServer(this) + , + httpServer(this) #endif { #ifdef PANDA3DS_ENABLE_DISCORD_RPC @@ -45,6 +46,10 @@ void Emulator::reset(ReloadOption reload) { cpu.reset(); gpu.reset(); memory.reset(); + // Reset scheduler and add a VBlank event + scheduler.reset(); + scheduler.addEvent(Scheduler::EventType::VBlank, CPU::ticksPerSec / 60); + // Kernel must be reset last because it depends on CPU/Memory state kernel.reset(); @@ -99,12 +104,6 @@ void Emulator::runFrame() { if (running) { cpu.runFrame(); // Run 1 frame of instructions gpu.display(); // Display graphics - lua.signalEvent(LuaEvent::Frame); - - // Send VBlank interrupts - ServiceManager& srv = kernel.getServiceManager(); - srv.sendGPUInterrupt(GPUInterrupt::VBlank0); - srv.sendGPUInterrupt(GPUInterrupt::VBlank1); // Run cheats if any are loaded if (cheats.haveCheats()) [[unlikely]] { @@ -117,6 +116,41 @@ void Emulator::runFrame() { } } +void Emulator::pollScheduler() { + auto& events = scheduler.events; + + // Pop events until there's none pending anymore + 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()); + + scheduler.updateNextTimestamp(); + + switch (eventType) { + case Scheduler::EventType::VBlank: { + // Signal that we've reached the end of a frame + frameDone = true; + lua.signalEvent(LuaEvent::Frame); + + // Send VBlank interrupts + ServiceManager& srv = kernel.getServiceManager(); + srv.sendGPUInterrupt(GPUInterrupt::VBlank0); + srv.sendGPUInterrupt(GPUInterrupt::VBlank1); + + // Queue next VBlank event + scheduler.addEvent(Scheduler::EventType::VBlank, time + CPU::ticksPerSec / 60); + break; + } + + default: { + Helpers::panic("Scheduler: Unimplemented event type received: %d\n", static_cast(eventType)); + break; + } + } + } +} + bool Emulator::loadROM(const std::filesystem::path& path) { // Reset the emulator if we've already loaded a ROM if (romType != ROMType::None) { From 0be099d1eae87f15c1ae0b73f6bbd6c279310752 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 22 Jan 2024 04:04:05 +0200 Subject: [PATCH 4/7] Hook up KTimer to scheduler --- include/cpu_dynarmic.hpp | 6 +++- include/kernel/kernel.hpp | 2 +- include/kernel/kernel_types.hpp | 5 ++- include/scheduler.hpp | 38 +++++++++++++++++++- src/core/CPU/cpu_dynarmic.cpp | 4 ++- src/core/kernel/timers.cpp | 63 +++++++++++++++++++++++---------- src/emulator.cpp | 5 ++- 7 files changed, 96 insertions(+), 27 deletions(-) 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 #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(EventType::TotalNumberOfEvents); + static constexpr u64 arm11Clock = 268111856; template using EventMap = boost::container::flat_multimap, boost::container::static_vector, size>>; @@ -46,4 +49,37 @@ struct Scheduler { // Add a dummy event to always keep the scheduler non-empty addEvent(EventType::Panic, std::numeric_limits::max()); } + + private: + static constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits::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(MAX_VALUE_TO_MULTIPLY)) { + return std::numeric_limits::max(); + } + + if (ns > static_cast(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::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 + #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::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(); + + 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(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(); 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(eventType)); break; From af996c55bae29d53cd02d5e973d08c03c6029cc0 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 22 Jan 2024 04:10:47 +0200 Subject: [PATCH 5/7] Remove debug printfs --- src/core/CPU/cpu_dynarmic.cpp | 2 -- src/core/kernel/timers.cpp | 1 - src/emulator.cpp | 1 - 3 files changed, 4 deletions(-) diff --git a/src/core/CPU/cpu_dynarmic.cpp b/src/core/CPU/cpu_dynarmic.cpp index 94ce335e..da5270b4 100644 --- a/src/core/CPU/cpu_dynarmic.cpp +++ b/src/core/CPU/cpu_dynarmic.cpp @@ -54,8 +54,6 @@ 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 f245f9dc..35fc57a4 100644 --- a/src/core/kernel/timers.cpp +++ b/src/core/kernel/timers.cpp @@ -57,7 +57,6 @@ void Kernel::cancelTimer(Timer* timer) { } void Kernel::signalTimer(Handle timerHandle, Timer* timer) { - printf("DEEPFRIED\n"); timer->fired = true; requireReschedule(); diff --git a/src/emulator.cpp b/src/emulator.cpp index 058fe868..098aba8c 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -129,7 +129,6 @@ 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); From 88dc2cc8647901e9af036df3f260225c65fbd47e Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 22 Jan 2024 14:57:02 +0200 Subject: [PATCH 6/7] Remove std::ranges usage --- include/scheduler.hpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/include/scheduler.hpp b/include/scheduler.hpp index 69c91f06..92328878 100644 --- a/include/scheduler.hpp +++ b/include/scheduler.hpp @@ -2,7 +2,6 @@ #include #include #include -#include #include "helpers.hpp" #include "logger.hpp" @@ -33,11 +32,14 @@ struct Scheduler { } void removeEvent(EventType type) { - auto it = std::ranges::find_if(events, [type](decltype(events)::const_reference pair) { return pair.second == type; }); - - if (it != events.end()) { - events.erase(it); - updateNextTimestamp(); + for (auto it = events.begin(); it != events.end(); it++) { + // Find first event of type "type" and remove it. + // Our scheduler shouldn't have duplicate events, so it's safe to exit when an event is found + if (it->second == type) { + events.erase(it); + updateNextTimestamp(); + break; + } } }; From 3c06098ead6c913117f014fe9e06b58c4096d815 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:07:52 +0200 Subject: [PATCH 7/7] Mark VBlank event likely --- src/emulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emulator.cpp b/src/emulator.cpp index f0f24c59..1ac8d5b2 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -128,7 +128,7 @@ void Emulator::pollScheduler() { scheduler.updateNextTimestamp(); switch (eventType) { - case Scheduler::EventType::VBlank: { + case Scheduler::EventType::VBlank: [[likely]] { // Signal that we've reached the end of a frame frameDone = true; lua.signalEvent(LuaEvent::Frame);