diff --git a/src/common/thread.cpp b/src/common/thread.cpp index 0cd2d10bff..c9684aed93 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -70,6 +70,12 @@ void SetCurrentThreadName(const char* name) { } #endif +#if defined(_WIN32) +void SetCurrentThreadName(const char* name) { + // Do Nothing on MingW +} +#endif + #endif } // namespace Common diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index efbad628fc..552094ddbf 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -7,6 +7,8 @@ endif() add_library(core STATIC arm/arm_interface.h arm/arm_interface.cpp + arm/cpu_interrupt_handler.cpp + arm/cpu_interrupt_handler.h arm/exclusive_monitor.cpp arm/exclusive_monitor.h arm/unicorn/arm_unicorn.cpp @@ -547,8 +549,6 @@ add_library(core STATIC hle/service/vi/vi_u.h hle/service/wlan/wlan.cpp hle/service/wlan/wlan.h - host_timing.cpp - host_timing.h loader/deconstructed_rom_directory.cpp loader/deconstructed_rom_directory.h loader/elf.cpp diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index cb2e640e2b..87a1c29cc9 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -18,11 +18,13 @@ enum class VMAPermission : u8; namespace Core { class System; +class CPUInterruptHandler; /// Generic ARMv8 CPU interface class ARM_Interface : NonCopyable { public: - explicit ARM_Interface(System& system_) : system{system_} {} + explicit ARM_Interface(System& system_, CPUInterruptHandler& interrupt_handler) + : system{system_}, interrupt_handler{interrupt_handler} {} virtual ~ARM_Interface() = default; struct ThreadContext32 { @@ -175,6 +177,7 @@ public: protected: /// System context that this ARM interface is running under. System& system; + CPUInterruptHandler& interrupt_handler; }; } // namespace Core diff --git a/src/core/arm/cpu_interrupt_handler.cpp b/src/core/arm/cpu_interrupt_handler.cpp new file mode 100644 index 0000000000..2f1a1a269b --- /dev/null +++ b/src/core/arm/cpu_interrupt_handler.cpp @@ -0,0 +1,29 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/thread.h" +#include "core/arm/cpu_interrupt_handler.h" + +namespace Core { + +CPUInterruptHandler::CPUInterruptHandler() : is_interrupted{} { + interrupt_event = std::make_unique(); +} + +CPUInterruptHandler::~CPUInterruptHandler() = default; + +void CPUInterruptHandler::SetInterrupt(bool is_interrupted_) { + if (is_interrupted_) { + interrupt_event->Set(); + } + this->is_interrupted = is_interrupted_; +} + +void CPUInterruptHandler::AwaitInterrupt() { + interrupt_event->Wait(); +} + +} // namespace Core diff --git a/src/core/arm/cpu_interrupt_handler.h b/src/core/arm/cpu_interrupt_handler.h new file mode 100644 index 0000000000..91c31a2710 --- /dev/null +++ b/src/core/arm/cpu_interrupt_handler.h @@ -0,0 +1,39 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +namespace Common { +class Event; +} + +namespace Core { + +class CPUInterruptHandler { +public: + CPUInterruptHandler(); + ~CPUInterruptHandler(); + + CPUInterruptHandler(const CPUInterruptHandler&) = delete; + CPUInterruptHandler& operator=(const CPUInterruptHandler&) = delete; + + CPUInterruptHandler(CPUInterruptHandler&&) = default; + CPUInterruptHandler& operator=(CPUInterruptHandler&&) = default; + + constexpr bool IsInterrupted() const { + return is_interrupted; + } + + void SetInterrupt(bool is_interrupted); + + void AwaitInterrupt(); + +private: + bool is_interrupted{}; + std::unique_ptr interrupt_event; +}; + +} // namespace Core diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index 4c8663d03c..0b7aa6a693 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -114,9 +114,9 @@ void ARM_Dynarmic_32::Step() { jit->Step(); } -ARM_Dynarmic_32::ARM_Dynarmic_32(System& system, ExclusiveMonitor& exclusive_monitor, - std::size_t core_index) - : ARM_Interface{system}, cb(std::make_unique(*this)), +ARM_Dynarmic_32::ARM_Dynarmic_32(System& system, CPUInterruptHandler& interrupt_handler, + ExclusiveMonitor& exclusive_monitor, std::size_t core_index) + : ARM_Interface{system, interrupt_handler}, cb(std::make_unique(*this)), cp15(std::make_shared(*this)), core_index{core_index}, exclusive_monitor{dynamic_cast(exclusive_monitor)} {} diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h index e5b92d7bb0..1e7e17e648 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.h +++ b/src/core/arm/dynarmic/arm_dynarmic_32.h @@ -21,6 +21,7 @@ class Memory; namespace Core { +class CPUInterruptHandler; class DynarmicCallbacks32; class DynarmicCP15; class DynarmicExclusiveMonitor; @@ -28,7 +29,8 @@ class System; class ARM_Dynarmic_32 final : public ARM_Interface { public: - ARM_Dynarmic_32(System& system, ExclusiveMonitor& exclusive_monitor, std::size_t core_index); + ARM_Dynarmic_32(System& system, CPUInterruptHandler& interrupt_handler, + ExclusiveMonitor& exclusive_monitor, std::size_t core_index); ~ARM_Dynarmic_32() override; void SetPC(u64 pc) override; diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index 5f5e36d94b..5e316ffd4d 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -9,6 +9,7 @@ #include "common/logging/log.h" #include "common/microprofile.h" #include "common/page_table.h" +#include "core/arm/cpu_interrupt_handler.h" #include "core/arm/dynarmic/arm_dynarmic_64.h" #include "core/core.h" #include "core/core_manager.h" @@ -108,23 +109,16 @@ public: } void AddTicks(u64 ticks) override { - // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a - // rough approximation of the amount of executed ticks in the system, it may be thrown off - // if not all cores are doing a similar amount of work. Instead of doing this, we should - // device a way so that timing is consistent across all cores without increasing the ticks 4 - // times. - u64 amortized_ticks = (ticks - num_interpreted_instructions) / Core::NUM_CPU_CORES; - // Always execute at least one tick. - amortized_ticks = std::max(amortized_ticks, 1); - - parent.system.CoreTiming().AddTicks(amortized_ticks); - num_interpreted_instructions = 0; + /// We are using host timing, NOP } u64 GetTicksRemaining() override { - return std::max(parent.system.CoreTiming().GetDowncount(), s64{0}); + if (!parent.interrupt_handler.IsInterrupted()) { + return 1000ULL; + } + return 0ULL; } u64 GetCNTPCT() override { - return Timing::CpuCyclesToClockCycles(parent.system.CoreTiming().GetTicks()); + return parent.system.CoreTiming().GetClockTicks(); } ARM_Dynarmic_64& parent; @@ -183,10 +177,10 @@ void ARM_Dynarmic_64::Step() { cb->InterpreterFallback(jit->GetPC(), 1); } -ARM_Dynarmic_64::ARM_Dynarmic_64(System& system, ExclusiveMonitor& exclusive_monitor, - std::size_t core_index) - : ARM_Interface{system}, cb(std::make_unique(*this)), - inner_unicorn{system, ARM_Unicorn::Arch::AArch64}, core_index{core_index}, +ARM_Dynarmic_64::ARM_Dynarmic_64(System& system, CPUInterruptHandler& interrupt_handler, + ExclusiveMonitor& exclusive_monitor, std::size_t core_index) + : ARM_Interface{system, interrupt_handler}, cb(std::make_unique(*this)), + inner_unicorn{system, interrupt_handler, ARM_Unicorn::Arch::AArch64}, core_index{core_index}, exclusive_monitor{dynamic_cast(exclusive_monitor)} {} ARM_Dynarmic_64::~ARM_Dynarmic_64() = default; diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h index 647cecaf0c..9e94b58c2d 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.h +++ b/src/core/arm/dynarmic/arm_dynarmic_64.h @@ -22,12 +22,14 @@ class Memory; namespace Core { class DynarmicCallbacks64; +class CPUInterruptHandler; class DynarmicExclusiveMonitor; class System; class ARM_Dynarmic_64 final : public ARM_Interface { public: - ARM_Dynarmic_64(System& system, ExclusiveMonitor& exclusive_monitor, std::size_t core_index); + ARM_Dynarmic_64(System& system, CPUInterruptHandler& interrupt_handler, + ExclusiveMonitor& exclusive_monitor, std::size_t core_index); ~ARM_Dynarmic_64() override; void SetPC(u64 pc) override; diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp index e40e9626a3..0393fe641d 100644 --- a/src/core/arm/unicorn/arm_unicorn.cpp +++ b/src/core/arm/unicorn/arm_unicorn.cpp @@ -6,6 +6,7 @@ #include #include "common/assert.h" #include "common/microprofile.h" +#include "core/arm/cpu_interrupt_handler.h" #include "core/arm/unicorn/arm_unicorn.h" #include "core/core.h" #include "core/core_timing.h" @@ -62,7 +63,8 @@ static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int si return false; } -ARM_Unicorn::ARM_Unicorn(System& system, Arch architecture) : ARM_Interface{system} { +ARM_Unicorn::ARM_Unicorn(System& system, CPUInterruptHandler& interrupt_handler, Arch architecture) + : ARM_Interface{system, interrupt_handler} { const auto arch = architecture == Arch::AArch32 ? UC_ARCH_ARM : UC_ARCH_ARM64; CHECKED(uc_open(arch, UC_MODE_ARM, &uc)); @@ -160,8 +162,12 @@ void ARM_Unicorn::Run() { if (GDBStub::IsServerEnabled()) { ExecuteInstructions(std::max(4000000U, 0U)); } else { - ExecuteInstructions( - std::max(std::size_t(system.CoreTiming().GetDowncount()), std::size_t{0})); + while (true) { + if (interrupt_handler.IsInterrupted()) { + return; + } + ExecuteInstructions(10); + } } } @@ -183,8 +189,6 @@ void ARM_Unicorn::ExecuteInstructions(std::size_t num_instructions) { UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC, page_buffer.data())); CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions)); CHECKED(uc_mem_unmap(uc, map_addr, page_buffer.size())); - - system.CoreTiming().AddTicks(num_instructions); if (GDBStub::IsServerEnabled()) { if (last_bkpt_hit && last_bkpt.type == GDBStub::BreakpointType::Execute) { uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address); diff --git a/src/core/arm/unicorn/arm_unicorn.h b/src/core/arm/unicorn/arm_unicorn.h index 725c650851..0a4c087cd8 100644 --- a/src/core/arm/unicorn/arm_unicorn.h +++ b/src/core/arm/unicorn/arm_unicorn.h @@ -11,6 +11,7 @@ namespace Core { +class CPUInterruptHandler; class System; class ARM_Unicorn final : public ARM_Interface { @@ -20,7 +21,7 @@ public: AArch64, // 64-bit ARM }; - explicit ARM_Unicorn(System& system, Arch architecture); + explicit ARM_Unicorn(System& system, CPUInterruptHandler& interrupt_handler, Arch architecture); ~ARM_Unicorn() override; void SetPC(u64 pc) override; diff --git a/src/core/core.cpp b/src/core/core.cpp index f9f8a30009..e8936b09d5 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -11,7 +11,6 @@ #include "common/string_util.h" #include "core/arm/exclusive_monitor.h" #include "core/core.h" -#include "core/core_manager.h" #include "core/core_timing.h" #include "core/cpu_manager.h" #include "core/device_memory.h" @@ -117,23 +116,30 @@ struct System::Impl { : kernel{system}, fs_controller{system}, memory{system}, cpu_manager{system}, reporter{system}, applet_manager{system} {} - CoreManager& CurrentCoreManager() { - return cpu_manager.GetCurrentCoreManager(); - } - Kernel::PhysicalCore& CurrentPhysicalCore() { - const auto index = cpu_manager.GetActiveCoreIndex(); - return kernel.PhysicalCore(index); + return kernel.CurrentPhysicalCore(); } Kernel::PhysicalCore& GetPhysicalCore(std::size_t index) { return kernel.PhysicalCore(index); } - ResultStatus RunLoop(bool tight_loop) { + ResultStatus Run() { status = ResultStatus::Success; - cpu_manager.RunLoop(tight_loop); + kernel.Suspend(false); + core_timing.SyncPause(false); + cpu_manager.Pause(false); + + return status; + } + + ResultStatus Pause() { + status = ResultStatus::Success; + + kernel.Suspend(true); + core_timing.SyncPause(true); + cpu_manager.Pause(true); return status; } @@ -143,7 +149,7 @@ struct System::Impl { device_memory = std::make_unique(system); - core_timing.Initialize(); + core_timing.Initialize([&system]() { system.RegisterHostThread(); }); kernel.Initialize(); cpu_manager.Initialize(); @@ -387,20 +393,24 @@ struct System::Impl { System::System() : impl{std::make_unique(*this)} {} System::~System() = default; -CoreManager& System::CurrentCoreManager() { - return impl->CurrentCoreManager(); +CpuManager& System::GetCpuManager() { + return impl->cpu_manager; } -const CoreManager& System::CurrentCoreManager() const { - return impl->CurrentCoreManager(); +const CpuManager& System::GetCpuManager() const { + return impl->cpu_manager; } -System::ResultStatus System::RunLoop(bool tight_loop) { - return impl->RunLoop(tight_loop); +System::ResultStatus System::Run() { + return impl->Run(); +} + +System::ResultStatus System::Pause() { + return impl->Pause(); } System::ResultStatus System::SingleStep() { - return RunLoop(false); + return ResultStatus::Success; } void System::InvalidateCpuInstructionCaches() { @@ -444,7 +454,9 @@ const ARM_Interface& System::CurrentArmInterface() const { } std::size_t System::CurrentCoreIndex() const { - return impl->cpu_manager.GetActiveCoreIndex(); + std::size_t core = impl->kernel.GetCurrentHostThreadID(); + ASSERT(core < Core::Hardware::NUM_CPU_CORES); + return core; } Kernel::Scheduler& System::CurrentScheduler() { @@ -497,15 +509,6 @@ const ARM_Interface& System::ArmInterface(std::size_t core_index) const { return impl->GetPhysicalCore(core_index).ArmInterface(); } -CoreManager& System::GetCoreManager(std::size_t core_index) { - return impl->cpu_manager.GetCoreManager(core_index); -} - -const CoreManager& System::GetCoreManager(std::size_t core_index) const { - ASSERT(core_index < NUM_CPU_CORES); - return impl->cpu_manager.GetCoreManager(core_index); -} - ExclusiveMonitor& System::Monitor() { return impl->kernel.GetExclusiveMonitor(); } diff --git a/src/core/core.h b/src/core/core.h index acc53d6a1f..7f170fc54e 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -90,7 +90,7 @@ class InterruptManager; namespace Core { class ARM_Interface; -class CoreManager; +class CpuManager; class DeviceMemory; class ExclusiveMonitor; class FrameLimiter; @@ -136,16 +136,18 @@ public: }; /** - * Run the core CPU loop - * This function runs the core for the specified number of CPU instructions before trying to - * update hardware. This is much faster than SingleStep (and should be equivalent), as the CPU - * is not required to do a full dispatch with each instruction. NOTE: the number of instructions - * requested is not guaranteed to run, as this will be interrupted preemptively if a hardware - * update is requested (e.g. on a thread switch). - * @param tight_loop If false, the CPU single-steps. - * @return Result status, indicating whether or not the operation succeeded. + * Run the OS and Application + * This function will start emulation and run the competent devices */ - ResultStatus RunLoop(bool tight_loop = true); + ResultStatus Run(); + + /** + * Pause the OS and Application + * This function will pause emulation and stop the competent devices + */ + ResultStatus Pause(); + + /** * Step the CPU one instruction @@ -215,11 +217,9 @@ public: /// Gets a const reference to an ARM interface from the CPU core with the specified index const ARM_Interface& ArmInterface(std::size_t core_index) const; - /// Gets a CPU interface to the CPU core with the specified index - CoreManager& GetCoreManager(std::size_t core_index); + CpuManager& GetCpuManager(); - /// Gets a CPU interface to the CPU core with the specified index - const CoreManager& GetCoreManager(std::size_t core_index) const; + const CpuManager& GetCpuManager() const; /// Gets a reference to the exclusive monitor ExclusiveMonitor& Monitor(); @@ -373,12 +373,6 @@ public: private: System(); - /// Returns the currently running CPU core - CoreManager& CurrentCoreManager(); - - /// Returns the currently running CPU core - const CoreManager& CurrentCoreManager() const; - /** * Initialize the emulated system. * @param emu_window Reference to the host-system window used for video output and keyboard diff --git a/src/core/core_manager.cpp b/src/core/core_manager.cpp index b6b797c80b..45f0bb5470 100644 --- a/src/core/core_manager.cpp +++ b/src/core/core_manager.cpp @@ -34,7 +34,6 @@ void CoreManager::RunLoop(bool tight_loop) { // instead advance to the next event and try to yield to the next thread if (Kernel::GetCurrentThread() == nullptr) { LOG_TRACE(Core, "Core-{} idling", core_index); - core_timing.Idle(); } else { if (tight_loop) { physical_core.Run(); @@ -42,7 +41,6 @@ void CoreManager::RunLoop(bool tight_loop) { physical_core.Step(); } } - core_timing.Advance(); Reschedule(); } @@ -59,7 +57,7 @@ void CoreManager::Reschedule() { // Lock the global kernel mutex when we manipulate the HLE state std::lock_guard lock(HLE::g_hle_lock); - global_scheduler.SelectThread(core_index); + // global_scheduler.SelectThread(core_index); physical_core.Scheduler().TryDoContextSwitch(); } diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 46d4178c43..a3ce69790a 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -1,5 +1,5 @@ -// Copyright 2008 Dolphin Emulator Project / 2017 Citra Emulator Project -// Licensed under GPLv2+ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include "core/core_timing.h" @@ -10,20 +10,16 @@ #include #include "common/assert.h" -#include "common/thread.h" #include "core/core_timing_util.h" -#include "core/hardware_properties.h" namespace Core::Timing { -constexpr int MAX_SLICE_LENGTH = 10000; - std::shared_ptr CreateEvent(std::string name, TimedCallback&& callback) { return std::make_shared(std::move(callback), std::move(name)); } struct CoreTiming::Event { - s64 time; + u64 time; u64 fifo_order; u64 userdata; std::weak_ptr type; @@ -39,51 +35,74 @@ struct CoreTiming::Event { } }; -CoreTiming::CoreTiming() = default; +CoreTiming::CoreTiming() { + clock = + Common::CreateBestMatchingClock(Core::Hardware::BASE_CLOCK_RATE, Core::Hardware::CNTFREQ); +} + CoreTiming::~CoreTiming() = default; -void CoreTiming::Initialize() { - downcounts.fill(MAX_SLICE_LENGTH); - time_slice.fill(MAX_SLICE_LENGTH); - slice_length = MAX_SLICE_LENGTH; - global_timer = 0; - idled_cycles = 0; - current_context = 0; - - // The time between CoreTiming being initialized and the first call to Advance() is considered - // the slice boundary between slice -1 and slice 0. Dispatcher loops must call Advance() before - // executing the first cycle of each slice to prepare the slice length and downcount for - // that slice. - is_global_timer_sane = true; +void CoreTiming::ThreadEntry(CoreTiming& instance) { + std::string name = "yuzu:HostTiming"; + Common::SetCurrentThreadName(name.c_str()); + instance.on_thread_init(); + instance.ThreadLoop(); +} +void CoreTiming::Initialize(std::function&& on_thread_init_) { + on_thread_init = std::move(on_thread_init_); event_fifo_id = 0; - const auto empty_timed_callback = [](u64, s64) {}; ev_lost = CreateEvent("_lost_event", empty_timed_callback); + timer_thread = std::make_unique(ThreadEntry, std::ref(*this)); } void CoreTiming::Shutdown() { + paused = true; + shutting_down = true; + event.Set(); + timer_thread->join(); ClearPendingEvents(); + timer_thread.reset(); + has_started = false; } -void CoreTiming::ScheduleEvent(s64 cycles_into_future, const std::shared_ptr& event_type, - u64 userdata) { - std::lock_guard guard{inner_mutex}; - const s64 timeout = GetTicks() + cycles_into_future; +void CoreTiming::Pause(bool is_paused) { + paused = is_paused; +} - // If this event needs to be scheduled before the next advance(), force one early - if (!is_global_timer_sane) { - ForceExceptionCheck(cycles_into_future); +void CoreTiming::SyncPause(bool is_paused) { + if (is_paused == paused && paused_set == paused) { + return; } + Pause(is_paused); + event.Set(); + while (paused_set != is_paused) + ; +} + +bool CoreTiming::IsRunning() const { + return !paused_set; +} + +bool CoreTiming::HasPendingEvents() const { + return !(wait_set && event_queue.empty()); +} + +void CoreTiming::ScheduleEvent(s64 ns_into_future, const std::shared_ptr& event_type, + u64 userdata) { + basic_lock.lock(); + const u64 timeout = static_cast(GetGlobalTimeNs().count() + ns_into_future); event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type}); std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); + basic_lock.unlock(); + event.Set(); } void CoreTiming::UnscheduleEvent(const std::shared_ptr& event_type, u64 userdata) { - std::lock_guard guard{inner_mutex}; - + basic_lock.lock(); const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) { return e.type.lock().get() == event_type.get() && e.userdata == userdata; }); @@ -93,23 +112,23 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr& event_type, u event_queue.erase(itr, event_queue.end()); std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>()); } + basic_lock.unlock(); } -u64 CoreTiming::GetTicks() const { - u64 ticks = static_cast(global_timer); - if (!is_global_timer_sane) { - ticks += accumulated_ticks; - } - return ticks; +void CoreTiming::AddTicks(std::size_t core_index, u64 ticks) { + ticks_count[core_index] += ticks; } -u64 CoreTiming::GetIdleTicks() const { - return static_cast(idled_cycles); +void CoreTiming::ResetTicks(std::size_t core_index) { + ticks_count[core_index] = 0; } -void CoreTiming::AddTicks(u64 ticks) { - accumulated_ticks += ticks; - downcounts[current_context] -= static_cast(ticks); +u64 CoreTiming::GetCPUTicks() const { + return clock->GetCPUCycles(); +} + +u64 CoreTiming::GetClockTicks() const { + return clock->GetClockCycles(); } void CoreTiming::ClearPendingEvents() { @@ -117,7 +136,7 @@ void CoreTiming::ClearPendingEvents() { } void CoreTiming::RemoveEvent(const std::shared_ptr& event_type) { - std::lock_guard guard{inner_mutex}; + basic_lock.lock(); const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) { return e.type.lock().get() == event_type.get(); @@ -128,99 +147,64 @@ void CoreTiming::RemoveEvent(const std::shared_ptr& event_type) { event_queue.erase(itr, event_queue.end()); std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>()); } + basic_lock.unlock(); } -void CoreTiming::ForceExceptionCheck(s64 cycles) { - cycles = std::max(0, cycles); - if (downcounts[current_context] <= cycles) { - return; - } - - // downcount is always (much) smaller than MAX_INT so we can safely cast cycles to an int - // here. Account for cycles already executed by adjusting the g.slice_length - downcounts[current_context] = static_cast(cycles); -} - -std::optional CoreTiming::NextAvailableCore(const s64 needed_ticks) const { - const u64 original_context = current_context; - u64 next_context = (original_context + 1) % num_cpu_cores; - while (next_context != original_context) { - if (time_slice[next_context] >= needed_ticks) { - return {next_context}; - } else if (time_slice[next_context] >= 0) { - return std::nullopt; - } - next_context = (next_context + 1) % num_cpu_cores; - } - return std::nullopt; -} - -void CoreTiming::Advance() { - std::unique_lock guard(inner_mutex); - - const u64 cycles_executed = accumulated_ticks; - time_slice[current_context] = std::max(0, time_slice[current_context] - accumulated_ticks); - global_timer += cycles_executed; - - is_global_timer_sane = true; +std::optional CoreTiming::Advance() { + advance_lock.lock(); + basic_lock.lock(); + global_timer = GetGlobalTimeNs().count(); while (!event_queue.empty() && event_queue.front().time <= global_timer) { Event evt = std::move(event_queue.front()); std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>()); event_queue.pop_back(); - inner_mutex.unlock(); + basic_lock.unlock(); if (auto event_type{evt.type.lock()}) { event_type->callback(evt.userdata, global_timer - evt.time); } - inner_mutex.lock(); + basic_lock.lock(); } - is_global_timer_sane = false; - - // Still events left (scheduled in the future) if (!event_queue.empty()) { - const s64 needed_ticks = - std::min(event_queue.front().time - global_timer, MAX_SLICE_LENGTH); - const auto next_core = NextAvailableCore(needed_ticks); - if (next_core) { - downcounts[*next_core] = needed_ticks; + const u64 next_time = event_queue.front().time - global_timer; + basic_lock.unlock(); + advance_lock.unlock(); + return next_time; + } else { + basic_lock.unlock(); + advance_lock.unlock(); + return std::nullopt; + } +} + +void CoreTiming::ThreadLoop() { + has_started = true; + while (!shutting_down) { + while (!paused) { + paused_set = false; + const auto next_time = Advance(); + if (next_time) { + std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time); + event.WaitFor(next_time_ns); + } else { + wait_set = true; + event.Wait(); + } + wait_set = false; } + paused_set = true; } - - accumulated_ticks = 0; - - downcounts[current_context] = time_slice[current_context]; } -void CoreTiming::ResetRun() { - downcounts.fill(MAX_SLICE_LENGTH); - time_slice.fill(MAX_SLICE_LENGTH); - current_context = 0; - // Still events left (scheduled in the future) - if (!event_queue.empty()) { - const s64 needed_ticks = - std::min(event_queue.front().time - global_timer, MAX_SLICE_LENGTH); - downcounts[current_context] = needed_ticks; - } - - is_global_timer_sane = false; - accumulated_ticks = 0; -} - -void CoreTiming::Idle() { - accumulated_ticks += downcounts[current_context]; - idled_cycles += downcounts[current_context]; - downcounts[current_context] = 0; +std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const { + return clock->GetTimeNS(); } std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const { - return std::chrono::microseconds{GetTicks() * 1000000 / Hardware::BASE_CLOCK_RATE}; -} - -s64 CoreTiming::GetDowncount() const { - return downcounts[current_context]; + return clock->GetTimeUS(); } } // namespace Core::Timing diff --git a/src/core/core_timing.h b/src/core/core_timing.h index d50f4eb8a7..707c8ef0c2 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -1,19 +1,25 @@ -// Copyright 2008 Dolphin Emulator Project / 2017 Citra Emulator Project -// Licensed under GPLv2+ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once +#include #include #include #include #include #include #include +#include #include #include "common/common_types.h" +#include "common/spin_lock.h" +#include "common/thread.h" #include "common/threadsafe_queue.h" +#include "common/wall_clock.h" +#include "core/hardware_properties.h" namespace Core::Timing { @@ -56,16 +62,30 @@ public: /// CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is /// required to end slice - 1 and start slice 0 before the first cycle of code is executed. - void Initialize(); + void Initialize(std::function&& on_thread_init_); /// Tears down all timing related functionality. void Shutdown(); - /// After the first Advance, the slice lengths and the downcount will be reduced whenever an - /// event is scheduled earlier than the current values. - /// - /// Scheduling from a callback will not update the downcount until the Advance() completes. - void ScheduleEvent(s64 cycles_into_future, const std::shared_ptr& event_type, + /// Pauses/Unpauses the execution of the timer thread. + void Pause(bool is_paused); + + /// Pauses/Unpauses the execution of the timer thread and waits until paused. + void SyncPause(bool is_paused); + + /// Checks if core timing is running. + bool IsRunning() const; + + /// Checks if the timer thread has started. + bool HasStarted() const { + return has_started; + } + + /// Checks if there are any pending time events. + bool HasPendingEvents() const; + + /// Schedules an event in core timing + void ScheduleEvent(s64 ns_into_future, const std::shared_ptr& event_type, u64 userdata = 0); void UnscheduleEvent(const std::shared_ptr& event_type, u64 userdata); @@ -73,41 +93,24 @@ public: /// We only permit one event of each type in the queue at a time. void RemoveEvent(const std::shared_ptr& event_type); - void ForceExceptionCheck(s64 cycles); + void AddTicks(std::size_t core_index, u64 ticks); - /// This should only be called from the emu thread, if you are calling it any other thread, - /// you are doing something evil - u64 GetTicks() const; + void ResetTicks(std::size_t core_index); - u64 GetIdleTicks() const; + /// Returns current time in emulated CPU cycles + u64 GetCPUTicks() const; - void AddTicks(u64 ticks); - - /// Advance must be called at the beginning of dispatcher loops, not the end. Advance() ends - /// the previous timing slice and begins the next one, you must Advance from the previous - /// slice to the current one before executing any cycles. CoreTiming starts in slice -1 so an - /// Advance() is required to initialize the slice length before the first cycle of emulated - /// instructions is executed. - void Advance(); - - /// Pretend that the main CPU has executed enough cycles to reach the next event. - void Idle(); + /// Returns current time in emulated in Clock cycles + u64 GetClockTicks() const; + /// Returns current time in microseconds. std::chrono::microseconds GetGlobalTimeUs() const; - void ResetRun(); + /// Returns current time in nanoseconds. + std::chrono::nanoseconds GetGlobalTimeNs() const; - s64 GetDowncount() const; - - void SwitchContext(u64 new_context) { - current_context = new_context; - } - - bool CanCurrentContextRun() const { - return time_slice[current_context] > 0; - } - - std::optional NextAvailableCore(const s64 needed_ticks) const; + /// Checks for events manually and returns time in nanoseconds for next event, threadsafe. + std::optional Advance(); private: struct Event; @@ -115,21 +118,14 @@ private: /// Clear all pending events. This should ONLY be done on exit. void ClearPendingEvents(); - static constexpr u64 num_cpu_cores = 4; + static void ThreadEntry(CoreTiming& instance); + void ThreadLoop(); - s64 global_timer = 0; - s64 idled_cycles = 0; - s64 slice_length = 0; - u64 accumulated_ticks = 0; - std::array downcounts{}; - // Slice of time assigned to each core per run. - std::array time_slice{}; - u64 current_context = 0; + std::unique_ptr clock; - // Are we in a function that has been called from Advance() - // If events are scheduled from a function that gets called from Advance(), - // don't change slice_length and downcount. - bool is_global_timer_sane = false; + u64 global_timer = 0; + + std::chrono::nanoseconds start_point; // The queue is a min-heap using std::make_heap/push_heap/pop_heap. // We don't use std::priority_queue because we need to be able to serialize, unserialize and @@ -139,8 +135,18 @@ private: u64 event_fifo_id = 0; std::shared_ptr ev_lost; + Common::Event event{}; + Common::SpinLock basic_lock{}; + Common::SpinLock advance_lock{}; + std::unique_ptr timer_thread; + std::atomic paused{}; + std::atomic paused_set{}; + std::atomic wait_set{}; + std::atomic shutting_down{}; + std::atomic has_started{}; + std::function on_thread_init{}; - std::mutex inner_mutex; + std::array, Core::Hardware::NUM_CPU_CORES> ticks_count{}; }; /// Creates a core timing event with the given name and callback. diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp index 70ddbdcca7..4948509923 100644 --- a/src/core/cpu_manager.cpp +++ b/src/core/cpu_manager.cpp @@ -2,80 +2,192 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/fiber.h" +#include "common/thread.h" #include "core/arm/exclusive_monitor.h" #include "core/core.h" -#include "core/core_manager.h" #include "core/core_timing.h" #include "core/cpu_manager.h" #include "core/gdbstub/gdbstub.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/physical_core.h" +#include "core/hle/kernel/scheduler.h" +#include "core/hle/kernel/thread.h" namespace Core { CpuManager::CpuManager(System& system) : system{system} {} CpuManager::~CpuManager() = default; +void CpuManager::ThreadStart(CpuManager& cpu_manager, std::size_t core) { + cpu_manager.RunThread(core); +} + void CpuManager::Initialize() { - for (std::size_t index = 0; index < core_managers.size(); ++index) { - core_managers[index] = std::make_unique(system, index); + running_mode = true; + for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + core_data[core].host_thread = + std::make_unique(ThreadStart, std::ref(*this), core); } } void CpuManager::Shutdown() { - for (auto& cpu_core : core_managers) { - cpu_core.reset(); + running_mode = false; + Pause(false); + for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + core_data[core].host_thread->join(); } } -CoreManager& CpuManager::GetCoreManager(std::size_t index) { - return *core_managers.at(index); +void CpuManager::GuestThreadFunction(void* cpu_manager_) { + CpuManager* cpu_manager = static_cast(cpu_manager_); + cpu_manager->RunGuestThread(); } -const CoreManager& CpuManager::GetCoreManager(std::size_t index) const { - return *core_managers.at(index); +void CpuManager::IdleThreadFunction(void* cpu_manager_) { + CpuManager* cpu_manager = static_cast(cpu_manager_); + cpu_manager->RunIdleThread(); } -CoreManager& CpuManager::GetCurrentCoreManager() { - // Otherwise, use single-threaded mode active_core variable - return *core_managers[active_core]; +void CpuManager::SuspendThreadFunction(void* cpu_manager_) { + CpuManager* cpu_manager = static_cast(cpu_manager_); + cpu_manager->RunSuspendThread(); } -const CoreManager& CpuManager::GetCurrentCoreManager() const { - // Otherwise, use single-threaded mode active_core variable - return *core_managers[active_core]; +std::function CpuManager::GetGuestThreadStartFunc() { + return std::function(GuestThreadFunction); } -void CpuManager::RunLoop(bool tight_loop) { - if (GDBStub::IsServerEnabled()) { - GDBStub::HandlePacket(); +std::function CpuManager::GetIdleThreadStartFunc() { + return std::function(IdleThreadFunction); +} - // If the loop is halted and we want to step, use a tiny (1) number of instructions to - // execute. Otherwise, get out of the loop function. - if (GDBStub::GetCpuHaltFlag()) { - if (GDBStub::GetCpuStepFlag()) { - tight_loop = false; - } else { - return; +std::function CpuManager::GetSuspendThreadStartFunc() { + return std::function(SuspendThreadFunction); +} + +void* CpuManager::GetStartFuncParamater() { + return static_cast(this); +} + +void CpuManager::RunGuestThread() { + auto& kernel = system.Kernel(); + { + auto& sched = kernel.CurrentScheduler(); + sched.OnThreadStart(); + } + while (true) { + auto& physical_core = kernel.CurrentPhysicalCore(); + LOG_CRITICAL(Core_ARM, "Running Guest Thread"); + physical_core.Idle(); + LOG_CRITICAL(Core_ARM, "Leaving Guest Thread"); + // physical_core.Run(); + auto& scheduler = physical_core.Scheduler(); + scheduler.TryDoContextSwitch(); + } +} + +void CpuManager::RunIdleThread() { + auto& kernel = system.Kernel(); + while (true) { + auto& physical_core = kernel.CurrentPhysicalCore(); + LOG_CRITICAL(Core_ARM, "Running Idle Thread"); + physical_core.Idle(); + auto& scheduler = physical_core.Scheduler(); + scheduler.TryDoContextSwitch(); + } +} + +void CpuManager::RunSuspendThread() { + LOG_CRITICAL(Core_ARM, "Suspending Thread Entered"); + auto& kernel = system.Kernel(); + { + auto& sched = kernel.CurrentScheduler(); + sched.OnThreadStart(); + } + while (true) { + auto core = kernel.GetCurrentHostThreadID(); + auto& scheduler = kernel.CurrentScheduler(); + Kernel::Thread* current_thread = scheduler.GetCurrentThread(); + LOG_CRITICAL(Core_ARM, "Suspending Core {}", core); + Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[core].host_context); + LOG_CRITICAL(Core_ARM, "Unsuspending Core {}", core); + ASSERT(scheduler.ContextSwitchPending()); + ASSERT(core == kernel.GetCurrentHostThreadID()); + scheduler.TryDoContextSwitch(); + } +} + +void CpuManager::Pause(bool paused) { + if (!paused) { + bool all_not_barrier = false; + while (!all_not_barrier) { + all_not_barrier = true; + for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + all_not_barrier &= + !core_data[core].is_running.load() && core_data[core].initialized.load(); } } - } - - auto& core_timing = system.CoreTiming(); - core_timing.ResetRun(); - bool keep_running{}; - do { - keep_running = false; - for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) { - core_timing.SwitchContext(active_core); - if (core_timing.CanCurrentContextRun()) { - core_managers[active_core]->RunLoop(tight_loop); - } - keep_running |= core_timing.CanCurrentContextRun(); + for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + core_data[core].enter_barrier->Set(); } - } while (keep_running); - - if (GDBStub::IsServerEnabled()) { - GDBStub::SetCpuStepFlag(false); + if (paused_state.load()) { + bool all_barrier = false; + while (!all_barrier) { + all_barrier = true; + for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + all_barrier &= + core_data[core].is_paused.load() && core_data[core].initialized.load(); + } + } + for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + core_data[core].exit_barrier->Set(); + } + } + } else { + /// Wait until all cores are paused. + bool all_barrier = false; + while (!all_barrier) { + all_barrier = true; + for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + all_barrier &= + core_data[core].is_paused.load() && core_data[core].initialized.load(); + } + } + /// Don't release the barrier } + paused_state = paused; +} + +void CpuManager::RunThread(std::size_t core) { + /// Initialization + system.RegisterCoreThread(core); + std::string name = "yuzu:CoreHostThread_" + std::to_string(core); + Common::SetCurrentThreadName(name.c_str()); + auto& data = core_data[core]; + data.enter_barrier = std::make_unique(); + data.exit_barrier = std::make_unique(); + data.host_context = Common::Fiber::ThreadToFiber(); + data.is_running = false; + data.initialized = true; + /// Running + while (running_mode) { + data.is_running = false; + data.enter_barrier->Wait(); + auto& scheduler = system.Kernel().CurrentScheduler(); + Kernel::Thread* current_thread = scheduler.GetCurrentThread(); + data.is_running = true; + Common::Fiber::YieldTo(data.host_context, current_thread->GetHostContext()); + data.is_running = false; + data.is_paused = true; + data.exit_barrier->Wait(); + data.is_paused = false; + } + /// Time to cleanup + data.host_context->Exit(); + data.enter_barrier.reset(); + data.exit_barrier.reset(); + data.initialized = false; } } // namespace Core diff --git a/src/core/cpu_manager.h b/src/core/cpu_manager.h index 97554d1bb6..8103ae857d 100644 --- a/src/core/cpu_manager.h +++ b/src/core/cpu_manager.h @@ -5,12 +5,18 @@ #pragma once #include +#include #include +#include #include "core/hardware_properties.h" +namespace Common { +class Event; +class Fiber; +} // namespace Common + namespace Core { -class CoreManager; class System; class CpuManager { @@ -27,21 +33,40 @@ public: void Initialize(); void Shutdown(); - CoreManager& GetCoreManager(std::size_t index); - const CoreManager& GetCoreManager(std::size_t index) const; + void Pause(bool paused); - CoreManager& GetCurrentCoreManager(); - const CoreManager& GetCurrentCoreManager() const; - - std::size_t GetActiveCoreIndex() const { - return active_core; - } - - void RunLoop(bool tight_loop); + std::function GetGuestThreadStartFunc(); + std::function GetIdleThreadStartFunc(); + std::function GetSuspendThreadStartFunc(); + void* GetStartFuncParamater(); private: - std::array, Hardware::NUM_CPU_CORES> core_managers; - std::size_t active_core{}; ///< Active core, only used in single thread mode + static void GuestThreadFunction(void* cpu_manager); + static void IdleThreadFunction(void* cpu_manager); + static void SuspendThreadFunction(void* cpu_manager); + + void RunGuestThread(); + void RunIdleThread(); + void RunSuspendThread(); + + static void ThreadStart(CpuManager& cpu_manager, std::size_t core); + + void RunThread(std::size_t core); + + struct CoreData { + std::shared_ptr host_context; + std::unique_ptr enter_barrier; + std::unique_ptr exit_barrier; + std::atomic is_running; + std::atomic is_paused; + std::atomic initialized; + std::unique_ptr host_thread; + }; + + std::atomic running_mode{}; + std::atomic paused_state{}; + + std::array core_data{}; System& system; }; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 7655382fac..ba051a7d80 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -13,11 +13,13 @@ #include "common/assert.h" #include "common/logging/log.h" +#include "common/thread.h" #include "core/arm/arm_interface.h" #include "core/arm/exclusive_monitor.h" #include "core/core.h" #include "core/core_timing.h" #include "core/core_timing_util.h" +#include "core/cpu_manager.h" #include "core/device_memory.h" #include "core/hardware_properties.h" #include "core/hle/kernel/client_port.h" @@ -117,7 +119,9 @@ struct KernelCore::Impl { InitializeSystemResourceLimit(kernel); InitializeMemoryLayout(); InitializeThreads(); - InitializePreemption(); + InitializePreemption(kernel); + InitializeSchedulers(); + InitializeSuspendThreads(); } void Shutdown() { @@ -155,6 +159,12 @@ struct KernelCore::Impl { } } + void InitializeSchedulers() { + for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { + cores[i].Scheduler().Initialize(); + } + } + // Creates the default system resource limit void InitializeSystemResourceLimit(KernelCore& kernel) { system_resource_limit = ResourceLimit::Create(kernel); @@ -178,10 +188,13 @@ struct KernelCore::Impl { Core::Timing::CreateEvent("ThreadWakeupCallback", ThreadWakeupCallback); } - void InitializePreemption() { - preemption_event = - Core::Timing::CreateEvent("PreemptionCallback", [this](u64 userdata, s64 cycles_late) { - global_scheduler.PreemptThreads(); + void InitializePreemption(KernelCore& kernel) { + preemption_event = Core::Timing::CreateEvent( + "PreemptionCallback", [this, &kernel](u64 userdata, s64 cycles_late) { + { + SchedulerLock lock(kernel); + global_scheduler.PreemptThreads(); + } s64 time_interval = Core::Timing::msToCycles(std::chrono::milliseconds(10)); system.CoreTiming().ScheduleEvent(time_interval, preemption_event); }); @@ -190,6 +203,20 @@ struct KernelCore::Impl { system.CoreTiming().ScheduleEvent(time_interval, preemption_event); } + void InitializeSuspendThreads() { + for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { + std::string name = "Suspend Thread Id:" + std::to_string(i); + std::function init_func = + system.GetCpuManager().GetSuspendThreadStartFunc(); + void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater(); + ThreadType type = + static_cast(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_SUSPEND); + auto thread_res = Thread::Create(system, type, name, 0, 0, 0, static_cast(i), 0, + nullptr, std::move(init_func), init_func_parameter); + suspend_threads[i] = std::move(thread_res).Unwrap(); + } + } + void MakeCurrentProcess(Process* process) { current_process = process; @@ -201,7 +228,10 @@ struct KernelCore::Impl { core.SetIs64Bit(process->Is64BitProcess()); } - system.Memory().SetCurrentPageTable(*process); + u32 core_id = GetCurrentHostThreadID(); + if (core_id < Core::Hardware::NUM_CPU_CORES) { + system.Memory().SetCurrentPageTable(*process, core_id); + } } void RegisterCoreThread(std::size_t core_id) { @@ -219,7 +249,9 @@ struct KernelCore::Impl { std::unique_lock lock{register_thread_mutex}; const std::thread::id this_id = std::this_thread::get_id(); const auto it = host_thread_ids.find(this_id); - ASSERT(it == host_thread_ids.end()); + if (it != host_thread_ids.end()) { + return; + } host_thread_ids[this_id] = registered_thread_ids++; } @@ -343,6 +375,8 @@ struct KernelCore::Impl { std::shared_ptr irs_shared_mem; std::shared_ptr time_shared_mem; + std::array, Core::Hardware::NUM_CPU_CORES> suspend_threads{}; + // System context Core::System& system; }; @@ -412,6 +446,26 @@ const Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) const { return impl->cores[id]; } +Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() { + u32 core_id = impl->GetCurrentHostThreadID(); + ASSERT(core_id < Core::Hardware::NUM_CPU_CORES); + return impl->cores[core_id]; +} + +const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const { + u32 core_id = impl->GetCurrentHostThreadID(); + ASSERT(core_id < Core::Hardware::NUM_CPU_CORES); + return impl->cores[core_id]; +} + +Kernel::Scheduler& KernelCore::CurrentScheduler() { + return CurrentPhysicalCore().Scheduler(); +} + +const Kernel::Scheduler& KernelCore::CurrentScheduler() const { + return CurrentPhysicalCore().Scheduler(); +} + Kernel::Synchronization& KernelCore::Synchronization() { return impl->synchronization; } @@ -557,4 +611,20 @@ const Kernel::SharedMemory& KernelCore::GetTimeSharedMem() const { return *impl->time_shared_mem; } +void KernelCore::Suspend(bool in_suspention) { + const bool should_suspend = exception_exited || in_suspention; + { + SchedulerLock lock(*this); + ThreadStatus status = should_suspend ? ThreadStatus::Ready : ThreadStatus::WaitSleep; + for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { + impl->suspend_threads[i]->SetStatus(status); + } + } +} + +void KernelCore::ExceptionalExit() { + exception_exited = true; + Suspend(true); +} + } // namespace Kernel diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 83de1f5427..5d32a83294 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -110,6 +110,18 @@ public: /// Gets the an instance of the respective physical CPU core. const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const; + /// Gets the sole instance of the Scheduler at the current running core. + Kernel::Scheduler& CurrentScheduler(); + + /// Gets the sole instance of the Scheduler at the current running core. + const Kernel::Scheduler& CurrentScheduler() const; + + /// Gets the an instance of the current physical CPU core. + Kernel::PhysicalCore& CurrentPhysicalCore(); + + /// Gets the an instance of the current physical CPU core. + const Kernel::PhysicalCore& CurrentPhysicalCore() const; + /// Gets the an instance of the Synchronization Interface. Kernel::Synchronization& Synchronization(); @@ -191,6 +203,12 @@ public: /// Gets the shared memory object for Time services. const Kernel::SharedMemory& GetTimeSharedMem() const; + /// Suspend/unsuspend the OS. + void Suspend(bool in_suspention); + + /// Exceptional exit the OS. + void ExceptionalExit(); + private: friend class Object; friend class Process; @@ -219,6 +237,7 @@ private: struct Impl; std::unique_ptr impl; + bool exception_exited{}; }; } // namespace Kernel diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp index a150110767..69202540be 100644 --- a/src/core/hle/kernel/physical_core.cpp +++ b/src/core/hle/kernel/physical_core.cpp @@ -2,12 +2,15 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/assert.h" #include "common/logging/log.h" +#include "common/spin_lock.h" #include "core/arm/arm_interface.h" #ifdef ARCHITECTURE_x86_64 #include "core/arm/dynarmic/arm_dynarmic_32.h" #include "core/arm/dynarmic/arm_dynarmic_64.h" #endif +#include "core/arm/cpu_interrupt_handler.h" #include "core/arm/exclusive_monitor.h" #include "core/arm/unicorn/arm_unicorn.h" #include "core/core.h" @@ -19,21 +22,23 @@ namespace Kernel { PhysicalCore::PhysicalCore(Core::System& system, std::size_t id, Core::ExclusiveMonitor& exclusive_monitor) - : core_index{id} { + : interrupt_handler{}, core_index{id} { #ifdef ARCHITECTURE_x86_64 - arm_interface_32 = - std::make_unique(system, exclusive_monitor, core_index); - arm_interface_64 = - std::make_unique(system, exclusive_monitor, core_index); - + arm_interface_32 = std::make_unique(system, interrupt_handler, + exclusive_monitor, core_index); + arm_interface_64 = std::make_unique(system, interrupt_handler, + exclusive_monitor, core_index); #else using Core::ARM_Unicorn; - arm_interface_32 = std::make_unique(system, ARM_Unicorn::Arch::AArch32); - arm_interface_64 = std::make_unique(system, ARM_Unicorn::Arch::AArch64); + arm_interface_32 = + std::make_unique(system, interrupt_handler, ARM_Unicorn::Arch::AArch32); + arm_interface_64 = + std::make_unique(system, interrupt_handler, ARM_Unicorn::Arch::AArch64); LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); #endif scheduler = std::make_unique(system, core_index); + guard = std::make_unique(); } PhysicalCore::~PhysicalCore() = default; @@ -47,6 +52,10 @@ void PhysicalCore::Step() { arm_interface->Step(); } +void PhysicalCore::Idle() { + interrupt_handler.AwaitInterrupt(); +} + void PhysicalCore::Stop() { arm_interface->PrepareReschedule(); } @@ -63,4 +72,16 @@ void PhysicalCore::SetIs64Bit(bool is_64_bit) { } } +void PhysicalCore::Interrupt() { + guard->lock(); + interrupt_handler.SetInterrupt(true); + guard->unlock(); +} + +void PhysicalCore::ClearInterrupt() { + guard->lock(); + interrupt_handler.SetInterrupt(false); + guard->unlock(); +} + } // namespace Kernel diff --git a/src/core/hle/kernel/physical_core.h b/src/core/hle/kernel/physical_core.h index 3269166bec..c3da30b721 100644 --- a/src/core/hle/kernel/physical_core.h +++ b/src/core/hle/kernel/physical_core.h @@ -7,6 +7,12 @@ #include #include +#include "core/arm/cpu_interrupt_handler.h" + +namespace Common { + class SpinLock; +} + namespace Kernel { class Scheduler; } // namespace Kernel @@ -32,11 +38,24 @@ public: /// Execute current jit state void Run(); + /// Set this core in IdleState. + void Idle(); /// Execute a single instruction in current jit. void Step(); /// Stop JIT execution/exit void Stop(); + /// Interrupt this physical core. + void Interrupt(); + + /// Clear this core's interrupt + void ClearInterrupt(); + + /// Check if this core is interrupted + bool IsInterrupted() const { + return interrupt_handler.IsInterrupted(); + } + // Shutdown this physical core. void Shutdown(); @@ -71,11 +90,13 @@ public: void SetIs64Bit(bool is_64_bit); private: + Core::CPUInterruptHandler interrupt_handler; std::size_t core_index; std::unique_ptr arm_interface_32; std::unique_ptr arm_interface_64; std::unique_ptr scheduler; Core::ARM_Interface* arm_interface{}; + std::unique_ptr guard; }; } // namespace Kernel diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index c4c5199b14..7e26a54f41 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -30,14 +30,15 @@ namespace { /** * Sets up the primary application thread * + * @param system The system instance to create the main thread under. * @param owner_process The parent process for the main thread - * @param kernel The kernel instance to create the main thread under. * @param priority The priority to give the main thread */ -void SetupMainThread(Process& owner_process, KernelCore& kernel, u32 priority, VAddr stack_top) { +void SetupMainThread(Core::System& system, Process& owner_process, u32 priority, VAddr stack_top) { const VAddr entry_point = owner_process.PageTable().GetCodeRegionStart(); - auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0, - owner_process.GetIdealCore(), stack_top, owner_process); + ThreadType type = THREADTYPE_USER; + auto thread_res = Thread::Create(system, type, "main", entry_point, priority, 0, + owner_process.GetIdealCore(), stack_top, &owner_process); std::shared_ptr thread = std::move(thread_res).Unwrap(); @@ -48,8 +49,12 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, u32 priority, V thread->GetContext32().cpu_registers[1] = thread_handle; thread->GetContext64().cpu_registers[1] = thread_handle; + auto& kernel = system.Kernel(); // Threads by default are dormant, wake up the main thread so it runs when the scheduler fires - thread->ResumeFromWait(); + { + SchedulerLock lock{kernel}; + thread->SetStatus(ThreadStatus::Ready); + } } } // Anonymous namespace @@ -294,7 +299,7 @@ void Process::Run(s32 main_thread_priority, u64 stack_size) { ChangeStatus(ProcessStatus::Running); - SetupMainThread(*this, kernel, main_thread_priority, main_thread_stack_top); + SetupMainThread(system, *this, main_thread_priority, main_thread_stack_top); resource_limit->Reserve(ResourceType::Threads, 1); resource_limit->Reserve(ResourceType::PhysicalMemory, main_thread_stack_size); } diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp index 1140c72a34..5166020a00 100644 --- a/src/core/hle/kernel/scheduler.cpp +++ b/src/core/hle/kernel/scheduler.cpp @@ -11,11 +11,15 @@ #include #include "common/assert.h" +#include "common/bit_util.h" +#include "common/fiber.h" #include "common/logging/log.h" #include "core/arm/arm_interface.h" #include "core/core.h" #include "core/core_timing.h" +#include "core/cpu_manager.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/physical_core.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/time_manager.h" @@ -27,78 +31,108 @@ GlobalScheduler::GlobalScheduler(KernelCore& kernel) : kernel{kernel} {} GlobalScheduler::~GlobalScheduler() = default; void GlobalScheduler::AddThread(std::shared_ptr thread) { + global_list_guard.lock(); thread_list.push_back(std::move(thread)); + global_list_guard.unlock(); } void GlobalScheduler::RemoveThread(std::shared_ptr thread) { + global_list_guard.lock(); thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread), thread_list.end()); + global_list_guard.unlock(); } -void GlobalScheduler::UnloadThread(std::size_t core) { - Scheduler& sched = kernel.Scheduler(core); - sched.UnloadThread(); -} - -void GlobalScheduler::SelectThread(std::size_t core) { +u32 GlobalScheduler::SelectThreads() { const auto update_thread = [](Thread* thread, Scheduler& sched) { + sched.guard.lock(); if (thread != sched.selected_thread.get()) { if (thread == nullptr) { ++sched.idle_selection_count; } sched.selected_thread = SharedFrom(thread); } - sched.is_context_switch_pending = sched.selected_thread != sched.current_thread; + const bool reschedule_pending = sched.selected_thread != sched.current_thread; + sched.is_context_switch_pending = reschedule_pending; std::atomic_thread_fence(std::memory_order_seq_cst); + sched.guard.unlock(); + return reschedule_pending; }; - Scheduler& sched = kernel.Scheduler(core); - Thread* current_thread = nullptr; + if (!is_reselection_pending.load()) { + return 0; + } + std::array top_threads{}; + + u32 idle_cores{}; + // Step 1: Get top thread in schedule queue. - current_thread = scheduled_queue[core].empty() ? nullptr : scheduled_queue[core].front(); - if (current_thread) { - update_thread(current_thread, sched); - return; - } - // Step 2: Try selecting a suggested thread. - Thread* winner = nullptr; - std::set sug_cores; - for (auto thread : suggested_queue[core]) { - s32 this_core = thread->GetProcessorID(); - Thread* thread_on_core = nullptr; - if (this_core >= 0) { - thread_on_core = scheduled_queue[this_core].front(); + for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + Thread* top_thread = + scheduled_queue[core].empty() ? nullptr : scheduled_queue[core].front(); + if (top_thread != nullptr) { + // TODO(Blinkhawk): Implement Thread Pinning + } else { + idle_cores |= (1ul << core); } - if (this_core < 0 || thread != thread_on_core) { - winner = thread; - break; - } - sug_cores.insert(this_core); + top_threads[core] = top_thread; } - // if we got a suggested thread, select it, else do a second pass. - if (winner && winner->GetPriority() > 2) { - if (winner->IsRunning()) { - UnloadThread(static_cast(winner->GetProcessorID())); - } - TransferToCore(winner->GetPriority(), static_cast(core), winner); - update_thread(winner, sched); - return; - } - // Step 3: Select a suggested thread from another core - for (auto& src_core : sug_cores) { - auto it = scheduled_queue[src_core].begin(); - it++; - if (it != scheduled_queue[src_core].end()) { - Thread* thread_on_core = scheduled_queue[src_core].front(); - Thread* to_change = *it; - if (thread_on_core->IsRunning() || to_change->IsRunning()) { - UnloadThread(static_cast(src_core)); + + while (idle_cores != 0) { + u32 core_id = Common::CountTrailingZeroes32(idle_cores); + + if (!suggested_queue[core_id].empty()) { + std::array migration_candidates{}; + std::size_t num_candidates = 0; + auto iter = suggested_queue[core_id].begin(); + Thread* suggested = nullptr; + // Step 2: Try selecting a suggested thread. + while (iter != suggested_queue[core_id].end()) { + suggested = *iter; + iter++; + s32 suggested_core_id = suggested->GetProcessorID(); + Thread* top_thread = + suggested_core_id > 0 ? top_threads[suggested_core_id] : nullptr; + if (top_thread != suggested) { + if (top_thread != nullptr && + top_thread->GetPriority() < THREADPRIO_MAX_CORE_MIGRATION) { + suggested = nullptr; + break; + // There's a too high thread to do core migration, cancel + } + TransferToCore(suggested->GetPriority(), static_cast(core_id), suggested); + break; + } + migration_candidates[num_candidates++] = suggested_core_id; } - TransferToCore(thread_on_core->GetPriority(), static_cast(core), thread_on_core); - current_thread = thread_on_core; - break; + // Step 3: Select a suggested thread from another core + if (suggested == nullptr) { + for (std::size_t i = 0; i < num_candidates; i++) { + s32 candidate_core = migration_candidates[i]; + suggested = top_threads[candidate_core]; + auto it = scheduled_queue[candidate_core].begin(); + it++; + Thread* next = it != scheduled_queue[candidate_core].end() ? *it : nullptr; + if (next != nullptr) { + TransferToCore(suggested->GetPriority(), static_cast(core_id), + suggested); + top_threads[candidate_core] = next; + break; + } + } + } + top_threads[core_id] = suggested; + } + + idle_cores &= ~(1ul << core_id); + } + u32 cores_needing_context_switch{}; + for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + Scheduler& sched = kernel.Scheduler(core); + if (update_thread(top_threads[core], sched)) { + cores_needing_context_switch |= (1ul << core); } } - update_thread(current_thread, sched); + return cores_needing_context_switch; } bool GlobalScheduler::YieldThread(Thread* yielding_thread) { @@ -153,9 +187,6 @@ bool GlobalScheduler::YieldThreadAndBalanceLoad(Thread* yielding_thread) { if (winner != nullptr) { if (winner != yielding_thread) { - if (winner->IsRunning()) { - UnloadThread(static_cast(winner->GetProcessorID())); - } TransferToCore(winner->GetPriority(), s32(core_id), winner); } } else { @@ -195,9 +226,6 @@ bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread } if (winner != nullptr) { if (winner != yielding_thread) { - if (winner->IsRunning()) { - UnloadThread(static_cast(winner->GetProcessorID())); - } TransferToCore(winner->GetPriority(), static_cast(core_id), winner); } } else { @@ -213,7 +241,9 @@ void GlobalScheduler::PreemptThreads() { const u32 priority = preemption_priorities[core_id]; if (scheduled_queue[core_id].size(priority) > 0) { - scheduled_queue[core_id].front(priority)->IncrementYieldCount(); + if (scheduled_queue[core_id].size(priority) > 1) { + scheduled_queue[core_id].front(priority)->IncrementYieldCount(); + } scheduled_queue[core_id].yield(priority); if (scheduled_queue[core_id].size(priority) > 1) { scheduled_queue[core_id].front(priority)->IncrementYieldCount(); @@ -247,9 +277,6 @@ void GlobalScheduler::PreemptThreads() { } if (winner != nullptr) { - if (winner->IsRunning()) { - UnloadThread(static_cast(winner->GetProcessorID())); - } TransferToCore(winner->GetPriority(), s32(core_id), winner); current_thread = winner->GetPriority() <= current_thread->GetPriority() ? winner : current_thread; @@ -280,9 +307,6 @@ void GlobalScheduler::PreemptThreads() { } if (winner != nullptr) { - if (winner->IsRunning()) { - UnloadThread(static_cast(winner->GetProcessorID())); - } TransferToCore(winner->GetPriority(), s32(core_id), winner); current_thread = winner; } @@ -292,6 +316,28 @@ void GlobalScheduler::PreemptThreads() { } } +void GlobalScheduler::EnableInterruptAndSchedule(u32 cores_pending_reschedule, + Core::EmuThreadHandle global_thread) { + u32 current_core = global_thread.host_handle; + bool must_context_switch = global_thread.guest_handle != InvalidHandle && + (current_core < Core::Hardware::NUM_CPU_CORES); + while (cores_pending_reschedule != 0) { + u32 core = Common::CountTrailingZeroes32(cores_pending_reschedule); + ASSERT(core < Core::Hardware::NUM_CPU_CORES); + if (!must_context_switch || core != current_core) { + auto& phys_core = kernel.PhysicalCore(core); + phys_core.Interrupt(); + } else { + must_context_switch = true; + } + cores_pending_reschedule &= ~(1ul << core); + } + if (must_context_switch) { + auto& core_scheduler = kernel.CurrentScheduler(); + core_scheduler.TryDoContextSwitch(); + } +} + void GlobalScheduler::Suggest(u32 priority, std::size_t core, Thread* thread) { suggested_queue[core].add(thread, priority); } @@ -349,6 +395,108 @@ bool GlobalScheduler::AskForReselectionOrMarkRedundant(Thread* current_thread, } } +void GlobalScheduler::AdjustSchedulingOnStatus(Thread* thread, u32 old_flags) { + if (old_flags == thread->scheduling_state) { + return; + } + + if (static_cast(old_flags & static_cast(ThreadSchedMasks::LowMask)) == + ThreadSchedStatus::Runnable) { + // In this case the thread was running, now it's pausing/exitting + if (thread->processor_id >= 0) { + Unschedule(thread->current_priority, static_cast(thread->processor_id), thread); + } + + for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + if (core != static_cast(thread->processor_id) && + ((thread->affinity_mask >> core) & 1) != 0) { + Unsuggest(thread->current_priority, core, thread); + } + } + } else if (thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable) { + // The thread is now set to running from being stopped + if (thread->processor_id >= 0) { + Schedule(thread->current_priority, static_cast(thread->processor_id), thread); + } + + for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + if (core != static_cast(thread->processor_id) && + ((thread->affinity_mask >> core) & 1) != 0) { + Suggest(thread->current_priority, core, thread); + } + } + } + + SetReselectionPending(); +} + +void GlobalScheduler::AdjustSchedulingOnPriority(Thread* thread, u32 old_priority) { + if (thread->GetSchedulingStatus() != ThreadSchedStatus::Runnable) { + return; + } + if (thread->processor_id >= 0) { + Unschedule(old_priority, static_cast(thread->processor_id), thread); + } + + for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + if (core != static_cast(thread->processor_id) && + ((thread->affinity_mask >> core) & 1) != 0) { + Unsuggest(old_priority, core, thread); + } + } + + if (thread->processor_id >= 0) { + // TODO(Blinkhawk): compare it with current thread running on current core, instead of + // checking running + if (thread->IsRunning()) { + SchedulePrepend(thread->current_priority, static_cast(thread->processor_id), + thread); + } else { + Schedule(thread->current_priority, static_cast(thread->processor_id), thread); + } + } + + for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + if (core != static_cast(thread->processor_id) && + ((thread->affinity_mask >> core) & 1) != 0) { + Suggest(thread->current_priority, core, thread); + } + } + thread->IncrementYieldCount(); + SetReselectionPending(); +} + +void GlobalScheduler::AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask, + s32 old_core) { + if (thread->GetSchedulingStatus() != ThreadSchedStatus::Runnable || + thread->current_priority >= THREADPRIO_COUNT) { + return; + } + + for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + if (((old_affinity_mask >> core) & 1) != 0) { + if (core == static_cast(old_core)) { + Unschedule(thread->current_priority, core, thread); + } else { + Unsuggest(thread->current_priority, core, thread); + } + } + } + + for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { + if (((thread->affinity_mask >> core) & 1) != 0) { + if (core == static_cast(thread->processor_id)) { + Schedule(thread->current_priority, core, thread); + } else { + Suggest(thread->current_priority, core, thread); + } + } + } + + thread->IncrementYieldCount(); + SetReselectionPending(); +} + void GlobalScheduler::Shutdown() { for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { scheduled_queue[core].clear(); @@ -374,13 +522,12 @@ void GlobalScheduler::Unlock() { ASSERT(scope_lock > 0); return; } - for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { - SelectThread(i); - } + u32 cores_pending_reschedule = SelectThreads(); + Core::EmuThreadHandle leaving_thread = current_owner; current_owner = Core::EmuThreadHandle::InvalidHandle(); scope_lock = 1; inner_lock.unlock(); - // TODO(Blinkhawk): Setup the interrupts and change context on current core. + EnableInterruptAndSchedule(cores_pending_reschedule, leaving_thread); } Scheduler::Scheduler(Core::System& system, std::size_t core_id) @@ -393,56 +540,83 @@ bool Scheduler::HaveReadyThreads() const { } Thread* Scheduler::GetCurrentThread() const { - return current_thread.get(); + if (current_thread) { + return current_thread.get(); + } + return idle_thread.get(); } Thread* Scheduler::GetSelectedThread() const { return selected_thread.get(); } -void Scheduler::SelectThreads() { - system.GlobalScheduler().SelectThread(core_id); -} - u64 Scheduler::GetLastContextSwitchTicks() const { return last_context_switch_time; } void Scheduler::TryDoContextSwitch() { + auto& phys_core = system.Kernel().CurrentPhysicalCore(); + if (phys_core.IsInterrupted()) { + phys_core.ClearInterrupt(); + } + guard.lock(); if (is_context_switch_pending) { SwitchContext(); + } else { + guard.unlock(); } } -void Scheduler::UnloadThread() { - Thread* const previous_thread = GetCurrentThread(); - Process* const previous_process = system.Kernel().CurrentProcess(); +void Scheduler::OnThreadStart() { + SwitchContextStep2(); +} - UpdateLastContextSwitchTime(previous_thread, previous_process); +void Scheduler::SwitchContextStep2() { + Thread* previous_thread = current_thread.get(); + Thread* new_thread = selected_thread.get(); - // Save context for previous thread - if (previous_thread) { - system.ArmInterface(core_id).SaveContext(previous_thread->GetContext32()); - system.ArmInterface(core_id).SaveContext(previous_thread->GetContext64()); - // Save the TPIDR_EL0 system register in case it was modified. - previous_thread->SetTPIDR_EL0(system.ArmInterface(core_id).GetTPIDR_EL0()); + // Load context of new thread + Process* const previous_process = + previous_thread != nullptr ? previous_thread->GetOwnerProcess() : nullptr; - if (previous_thread->GetStatus() == ThreadStatus::Running) { - // This is only the case when a reschedule is triggered without the current thread - // yielding execution (i.e. an event triggered, system core time-sliced, etc) - previous_thread->SetStatus(ThreadStatus::Ready); + if (new_thread) { + new_thread->context_guard.lock(); + ASSERT_MSG(new_thread->GetProcessorID() == s32(this->core_id), + "Thread must be assigned to this core."); + ASSERT_MSG(new_thread->GetStatus() == ThreadStatus::Ready, + "Thread must be ready to become running."); + + // Cancel any outstanding wakeup events for this thread + current_thread = SharedFrom(new_thread); + new_thread->SetStatus(ThreadStatus::Running); + new_thread->SetIsRunning(true); + + auto* const thread_owner_process = current_thread->GetOwnerProcess(); + if (previous_process != thread_owner_process && thread_owner_process != nullptr) { + system.Kernel().MakeCurrentProcess(thread_owner_process); } - previous_thread->SetIsRunning(false); + if (!new_thread->IsHLEThread()) { + auto& cpu_core = system.ArmInterface(core_id); + cpu_core.LoadContext(new_thread->GetContext32()); + cpu_core.LoadContext(new_thread->GetContext64()); + cpu_core.SetTlsAddress(new_thread->GetTLSAddress()); + cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0()); + } + } else { + current_thread = nullptr; + // Note: We do not reset the current process and current page table when idling because + // technically we haven't changed processes, our threads are just paused. } - current_thread = nullptr; + guard.unlock(); } void Scheduler::SwitchContext() { - Thread* const previous_thread = GetCurrentThread(); - Thread* const new_thread = GetSelectedThread(); + Thread* previous_thread = current_thread.get(); + Thread* new_thread = selected_thread.get(); is_context_switch_pending = false; if (new_thread == previous_thread) { + guard.unlock(); return; } @@ -452,51 +626,44 @@ void Scheduler::SwitchContext() { // Save context for previous thread if (previous_thread) { - system.ArmInterface(core_id).SaveContext(previous_thread->GetContext32()); - system.ArmInterface(core_id).SaveContext(previous_thread->GetContext64()); - // Save the TPIDR_EL0 system register in case it was modified. - previous_thread->SetTPIDR_EL0(system.ArmInterface(core_id).GetTPIDR_EL0()); + if (!previous_thread->IsHLEThread()) { + auto& cpu_core = system.ArmInterface(core_id); + cpu_core.SaveContext(previous_thread->GetContext32()); + cpu_core.SaveContext(previous_thread->GetContext64()); + // Save the TPIDR_EL0 system register in case it was modified. + previous_thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0()); + } if (previous_thread->GetStatus() == ThreadStatus::Running) { - // This is only the case when a reschedule is triggered without the current thread - // yielding execution (i.e. an event triggered, system core time-sliced, etc) previous_thread->SetStatus(ThreadStatus::Ready); } previous_thread->SetIsRunning(false); + previous_thread->context_guard.unlock(); } - // Load context of new thread - if (new_thread) { - ASSERT_MSG(new_thread->GetProcessorID() == s32(this->core_id), - "Thread must be assigned to this core."); - ASSERT_MSG(new_thread->GetStatus() == ThreadStatus::Ready, - "Thread must be ready to become running."); - - // Cancel any outstanding wakeup events for this thread - new_thread->CancelWakeupTimer(); - current_thread = SharedFrom(new_thread); - new_thread->SetStatus(ThreadStatus::Running); - new_thread->SetIsRunning(true); - - auto* const thread_owner_process = current_thread->GetOwnerProcess(); - if (previous_process != thread_owner_process) { - system.Kernel().MakeCurrentProcess(thread_owner_process); - } - - system.ArmInterface(core_id).LoadContext(new_thread->GetContext32()); - system.ArmInterface(core_id).LoadContext(new_thread->GetContext64()); - system.ArmInterface(core_id).SetTlsAddress(new_thread->GetTLSAddress()); - system.ArmInterface(core_id).SetTPIDR_EL0(new_thread->GetTPIDR_EL0()); + std::shared_ptr old_context; + if (previous_thread != nullptr) { + old_context = previous_thread->GetHostContext(); } else { - current_thread = nullptr; - // Note: We do not reset the current process and current page table when idling because - // technically we haven't changed processes, our threads are just paused. + old_context = idle_thread->GetHostContext(); } + + std::shared_ptr next_context; + if (new_thread != nullptr) { + next_context = new_thread->GetHostContext(); + } else { + next_context = idle_thread->GetHostContext(); + } + + Common::Fiber::YieldTo(old_context, next_context); + /// When a thread wakes up, the scheduler may have changed to other in another core. + auto& next_scheduler = system.Kernel().CurrentScheduler(); + next_scheduler.SwitchContextStep2(); } void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) { const u64 prev_switch_ticks = last_context_switch_time; - const u64 most_recent_switch_ticks = system.CoreTiming().GetTicks(); + const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks(); const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks; if (thread != nullptr) { @@ -510,6 +677,16 @@ void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) { last_context_switch_time = most_recent_switch_ticks; } +void Scheduler::Initialize() { + std::string name = "Idle Thread Id:" + std::to_string(core_id); + std::function init_func = system.GetCpuManager().GetIdleThreadStartFunc(); + void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater(); + ThreadType type = static_cast(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_IDLE); + auto thread_res = Thread::Create(system, type, name, 0, 64, 0, static_cast(core_id), 0, + nullptr, std::move(init_func), init_func_parameter); + idle_thread = std::move(thread_res).Unwrap(); +} + void Scheduler::Shutdown() { current_thread = nullptr; selected_thread = nullptr; diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h index 07df33f9c3..16655b03fe 100644 --- a/src/core/hle/kernel/scheduler.h +++ b/src/core/hle/kernel/scheduler.h @@ -11,6 +11,7 @@ #include "common/common_types.h" #include "common/multi_level_queue.h" +#include "common/spin_lock.h" #include "core/hardware_properties.h" #include "core/hle/kernel/thread.h" @@ -41,41 +42,17 @@ public: return thread_list; } - /** - * Add a thread to the suggested queue of a cpu core. Suggested threads may be - * picked if no thread is scheduled to run on the core. - */ - void Suggest(u32 priority, std::size_t core, Thread* thread); + /// Notify the scheduler a thread's status has changed. + void AdjustSchedulingOnStatus(Thread* thread, u32 old_flags); + + /// Notify the scheduler a thread's priority has changed. + void AdjustSchedulingOnPriority(Thread* thread, u32 old_priority); + + /// Notify the scheduler a thread's core and/or affinity mask has changed. + void AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask, s32 old_core); /** - * Remove a thread to the suggested queue of a cpu core. Suggested threads may be - * picked if no thread is scheduled to run on the core. - */ - void Unsuggest(u32 priority, std::size_t core, Thread* thread); - - /** - * Add a thread to the scheduling queue of a cpu core. The thread is added at the - * back the queue in its priority level. - */ - void Schedule(u32 priority, std::size_t core, Thread* thread); - - /** - * Add a thread to the scheduling queue of a cpu core. The thread is added at the - * front the queue in its priority level. - */ - void SchedulePrepend(u32 priority, std::size_t core, Thread* thread); - - /// Reschedule an already scheduled thread based on a new priority - void Reschedule(u32 priority, std::size_t core, Thread* thread); - - /// Unschedules a thread. - void Unschedule(u32 priority, std::size_t core, Thread* thread); - - /// Selects a core and forces it to unload its current thread's context - void UnloadThread(std::size_t core); - - /** - * Takes care of selecting the new scheduled thread in three steps: + * Takes care of selecting the new scheduled threads in three steps: * * 1. First a thread is selected from the top of the priority queue. If no thread * is obtained then we move to step two, else we are done. @@ -85,8 +62,10 @@ public: * * 3. Third is no suggested thread is found, we do a second pass and pick a running * thread in another core and swap it with its current thread. + * + * returns the cores needing scheduling. */ - void SelectThread(std::size_t core); + u32 SelectThreads(); bool HaveReadyThreads(std::size_t core_id) const { return !scheduled_queue[core_id].empty(); @@ -149,6 +128,39 @@ private: /// Unlocks the scheduler, reselects threads, interrupts cores for rescheduling /// and reschedules current core if needed. void Unlock(); + + void EnableInterruptAndSchedule(u32 cores_pending_reschedule, Core::EmuThreadHandle global_thread); + + /** + * Add a thread to the suggested queue of a cpu core. Suggested threads may be + * picked if no thread is scheduled to run on the core. + */ + void Suggest(u32 priority, std::size_t core, Thread* thread); + + /** + * Remove a thread to the suggested queue of a cpu core. Suggested threads may be + * picked if no thread is scheduled to run on the core. + */ + void Unsuggest(u32 priority, std::size_t core, Thread* thread); + + /** + * Add a thread to the scheduling queue of a cpu core. The thread is added at the + * back the queue in its priority level. + */ + void Schedule(u32 priority, std::size_t core, Thread* thread); + + /** + * Add a thread to the scheduling queue of a cpu core. The thread is added at the + * front the queue in its priority level. + */ + void SchedulePrepend(u32 priority, std::size_t core, Thread* thread); + + /// Reschedule an already scheduled thread based on a new priority + void Reschedule(u32 priority, std::size_t core, Thread* thread); + + /// Unschedules a thread. + void Unschedule(u32 priority, std::size_t core, Thread* thread); + /** * Transfers a thread into an specific core. If the destination_core is -1 * it will be unscheduled from its source code and added into its suggested @@ -174,6 +186,8 @@ private: std::atomic scope_lock{}; Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()}; + Common::SpinLock global_list_guard{}; + /// Lists all thread ids that aren't deleted/etc. std::vector> thread_list; KernelCore& kernel; @@ -190,12 +204,6 @@ public: /// Reschedules to the next available thread (call after current thread is suspended) void TryDoContextSwitch(); - /// Unloads currently running thread - void UnloadThread(); - - /// Select the threads in top of the scheduling multilist. - void SelectThreads(); - /// Gets the current running thread Thread* GetCurrentThread() const; @@ -209,15 +217,22 @@ public: return is_context_switch_pending; } + void Initialize(); + /// Shutdowns the scheduler. void Shutdown(); + void OnThreadStart(); + private: friend class GlobalScheduler; /// Switches the CPU's active thread context to that of the specified thread void SwitchContext(); + /// When a thread wakes up, it must run this through it's new scheduler + void SwitchContextStep2(); + /** * Called on every context switch to update the internal timestamp * This also updates the running time ticks for the given thread and @@ -233,12 +248,15 @@ private: std::shared_ptr current_thread = nullptr; std::shared_ptr selected_thread = nullptr; + std::shared_ptr idle_thread = nullptr; Core::System& system; u64 last_context_switch_time = 0; u64 idle_selection_count = 0; const std::size_t core_id; + Common::SpinLock guard{}; + bool is_context_switch_pending = false; }; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 4ae4529f52..d7f0dcabd1 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -863,9 +863,9 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) { const u64 thread_ticks = current_thread->GetTotalCPUTimeTicks(); - out_ticks = thread_ticks + (core_timing.GetTicks() - prev_ctx_ticks); + out_ticks = thread_ticks + (core_timing.GetCPUTicks() - prev_ctx_ticks); } else if (same_thread && info_sub_id == system.CurrentCoreIndex()) { - out_ticks = core_timing.GetTicks() - prev_ctx_ticks; + out_ticks = core_timing.GetCPUTicks() - prev_ctx_ticks; } *result = out_ticks; @@ -1428,9 +1428,10 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e ASSERT(kernel.CurrentProcess()->GetResourceLimit()->Reserve(ResourceType::Threads, 1)); + ThreadType type = THREADTYPE_USER; CASCADE_RESULT(std::shared_ptr thread, - Thread::Create(kernel, "", entry_point, priority, arg, processor_id, stack_top, - *current_process)); + Thread::Create(system, type, "", entry_point, priority, arg, processor_id, stack_top, + current_process)); const auto new_thread_handle = current_process->GetHandleTable().Create(thread); if (new_thread_handle.Failed()) { @@ -1513,13 +1514,6 @@ static void SleepThread(Core::System& system, s64 nanoseconds) { } else { current_thread->Sleep(nanoseconds); } - - if (is_redundant) { - // If it's redundant, the core is pretty much idle. Some games keep idling - // a core while it's doing nothing, we advance timing to avoid costly continuous - // calls. - system.CoreTiming().AddTicks(2000); - } system.PrepareReschedule(current_thread->GetProcessorID()); } @@ -1725,10 +1719,7 @@ static u64 GetSystemTick(Core::System& system) { auto& core_timing = system.CoreTiming(); // Returns the value of cntpct_el0 (https://switchbrew.org/wiki/SVC#svcGetSystemTick) - const u64 result{Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks())}; - - // Advance time to defeat dumb games that busy-wait for the frame to end. - core_timing.AddTicks(400); + const u64 result{system.CoreTiming().GetClockTicks()}; return result; } diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index db7f379ac2..8cb3593dbe 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -9,12 +9,14 @@ #include "common/assert.h" #include "common/common_types.h" +#include "common/fiber.h" #include "common/logging/log.h" #include "common/thread_queue_list.h" #include "core/arm/arm_interface.h" #include "core/core.h" #include "core/core_timing.h" #include "core/core_timing_util.h" +#include "core/cpu_manager.h" #include "core/hardware_properties.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" @@ -23,6 +25,7 @@ #include "core/hle/kernel/process.h" #include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" +#include "core/hle/kernel/time_manager.h" #include "core/hle/result.h" #include "core/memory.h" @@ -44,6 +47,7 @@ Thread::Thread(KernelCore& kernel) : SynchronizationObject{kernel} {} Thread::~Thread() = default; void Thread::Stop() { + SchedulerLock lock(kernel); // Cancel any outstanding wakeup events for this thread Core::System::GetInstance().CoreTiming().UnscheduleEvent(kernel.ThreadWakeupCallbackEventType(), global_handle); @@ -71,9 +75,8 @@ void Thread::WakeAfterDelay(s64 nanoseconds) { // This function might be called from any thread so we have to be cautious and use the // thread-safe version of ScheduleEvent. - const s64 cycles = Core::Timing::nsToCycles(std::chrono::nanoseconds{nanoseconds}); Core::System::GetInstance().CoreTiming().ScheduleEvent( - cycles, kernel.ThreadWakeupCallbackEventType(), global_handle); + nanoseconds, kernel.ThreadWakeupCallbackEventType(), global_handle); } void Thread::CancelWakeupTimer() { @@ -125,6 +128,16 @@ void Thread::ResumeFromWait() { SetStatus(ThreadStatus::Ready); } +void Thread::OnWakeUp() { + SchedulerLock lock(kernel); + if (activity == ThreadActivity::Paused) { + SetStatus(ThreadStatus::Paused); + return; + } + + SetStatus(ThreadStatus::Ready); +} + void Thread::CancelWait() { if (GetSchedulingStatus() != ThreadSchedStatus::Paused) { is_sync_cancelled = true; @@ -153,12 +166,29 @@ static void ResetThreadContext64(Core::ARM_Interface::ThreadContext64& context, context.fpcr = 0; } -ResultVal> Thread::Create(KernelCore& kernel, std::string name, - VAddr entry_point, u32 priority, u64 arg, - s32 processor_id, VAddr stack_top, - Process& owner_process) { +std::shared_ptr Thread::GetHostContext() const { + return host_context; +} + +ResultVal> Thread::Create(Core::System& system, ThreadType type_flags, + std::string name, VAddr entry_point, u32 priority, + u64 arg, s32 processor_id, VAddr stack_top, + Process* owner_process) { + std::function init_func = system.GetCpuManager().GetGuestThreadStartFunc(); + void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater(); + return Create(system, type_flags, name, entry_point, priority, arg, processor_id, stack_top, + owner_process, std::move(init_func), init_func_parameter); +} + +ResultVal> Thread::Create(Core::System& system, ThreadType type_flags, + std::string name, VAddr entry_point, u32 priority, + u64 arg, s32 processor_id, VAddr stack_top, + Process* owner_process, + std::function&& thread_start_func, + void* thread_start_parameter) { + auto& kernel = system.Kernel(); // Check if priority is in ranged. Lowest priority -> highest priority id. - if (priority > THREADPRIO_LOWEST) { + if (priority > THREADPRIO_LOWEST && (type_flags & THREADTYPE_IDLE == 0)) { LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority); return ERR_INVALID_THREAD_PRIORITY; } @@ -168,11 +198,12 @@ ResultVal> Thread::Create(KernelCore& kernel, std::strin return ERR_INVALID_PROCESSOR_ID; } - auto& system = Core::System::GetInstance(); - if (!system.Memory().IsValidVirtualAddress(owner_process, entry_point)) { - LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point); - // TODO (bunnei): Find the correct error code to use here - return RESULT_UNKNOWN; + if (owner_process) { + if (!system.Memory().IsValidVirtualAddress(*owner_process, entry_point)) { + LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point); + // TODO (bunnei): Find the correct error code to use here + return RESULT_UNKNOWN; + } } std::shared_ptr thread = std::make_shared(kernel); @@ -183,7 +214,7 @@ ResultVal> Thread::Create(KernelCore& kernel, std::strin thread->stack_top = stack_top; thread->tpidr_el0 = 0; thread->nominal_priority = thread->current_priority = priority; - thread->last_running_ticks = system.CoreTiming().GetTicks(); + thread->last_running_ticks = 0; thread->processor_id = processor_id; thread->ideal_core = processor_id; thread->affinity_mask = 1ULL << processor_id; @@ -193,16 +224,27 @@ ResultVal> Thread::Create(KernelCore& kernel, std::strin thread->wait_handle = 0; thread->name = std::move(name); thread->global_handle = kernel.GlobalHandleTable().Create(thread).Unwrap(); - thread->owner_process = &owner_process; - auto& scheduler = kernel.GlobalScheduler(); - scheduler.AddThread(thread); - thread->tls_address = thread->owner_process->CreateTLSRegion(); - - thread->owner_process->RegisterThread(thread.get()); - - ResetThreadContext32(thread->context_32, static_cast(stack_top), - static_cast(entry_point), static_cast(arg)); - ResetThreadContext64(thread->context_64, stack_top, entry_point, arg); + thread->owner_process = owner_process; + thread->type = type_flags; + if ((type_flags & THREADTYPE_IDLE) == 0) { + auto& scheduler = kernel.GlobalScheduler(); + scheduler.AddThread(thread); + } + if (owner_process) { + thread->tls_address = thread->owner_process->CreateTLSRegion(); + thread->owner_process->RegisterThread(thread.get()); + } else { + thread->tls_address = 0; + } + // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used + // to initialize the context + if ((type_flags & THREADTYPE_HLE) == 0) { + ResetThreadContext32(thread->context_32, static_cast(stack_top), + static_cast(entry_point), static_cast(arg)); + ResetThreadContext64(thread->context_64, stack_top, entry_point, arg); + } + thread->host_context = + std::make_shared(std::move(thread_start_func), thread_start_parameter); return MakeResult>(std::move(thread)); } @@ -258,7 +300,7 @@ void Thread::SetStatus(ThreadStatus new_status) { } if (status == ThreadStatus::Running) { - last_running_ticks = Core::System::GetInstance().CoreTiming().GetTicks(); + last_running_ticks = Core::System::GetInstance().CoreTiming().GetCPUTicks(); } status = new_status; @@ -375,38 +417,55 @@ void Thread::SetActivity(ThreadActivity value) { } void Thread::Sleep(s64 nanoseconds) { - // Sleep current thread and check for next thread to schedule - SetStatus(ThreadStatus::WaitSleep); + Handle event_handle{}; + { + SchedulerLockAndSleep lock(kernel, event_handle, this, nanoseconds); + SetStatus(ThreadStatus::WaitSleep); + } - // Create an event to wake the thread up after the specified nanosecond delay has passed - WakeAfterDelay(nanoseconds); + if (event_handle != InvalidHandle) { + auto& time_manager = kernel.TimeManager(); + time_manager.UnscheduleTimeEvent(event_handle); + } } bool Thread::YieldSimple() { - auto& scheduler = kernel.GlobalScheduler(); - return scheduler.YieldThread(this); + bool result{}; + { + SchedulerLock lock(kernel); + result = kernel.GlobalScheduler().YieldThread(this); + } + return result; } bool Thread::YieldAndBalanceLoad() { - auto& scheduler = kernel.GlobalScheduler(); - return scheduler.YieldThreadAndBalanceLoad(this); + bool result{}; + { + SchedulerLock lock(kernel); + result = kernel.GlobalScheduler().YieldThreadAndBalanceLoad(this); + } + return result; } bool Thread::YieldAndWaitForLoadBalancing() { - auto& scheduler = kernel.GlobalScheduler(); - return scheduler.YieldThreadAndWaitForLoadBalancing(this); + bool result{}; + { + SchedulerLock lock(kernel); + result = kernel.GlobalScheduler().YieldThreadAndWaitForLoadBalancing(this); + } + return result; } void Thread::SetSchedulingStatus(ThreadSchedStatus new_status) { const u32 old_flags = scheduling_state; scheduling_state = (scheduling_state & static_cast(ThreadSchedMasks::HighMask)) | static_cast(new_status); - AdjustSchedulingOnStatus(old_flags); + kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_flags); } void Thread::SetCurrentPriority(u32 new_priority) { const u32 old_priority = std::exchange(current_priority, new_priority); - AdjustSchedulingOnPriority(old_priority); + kernel.GlobalScheduler().AdjustSchedulingOnPriority(this, old_priority); } ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) { @@ -443,111 +502,12 @@ ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) { processor_id = ideal_core; } } - AdjustSchedulingOnAffinity(old_affinity_mask, old_core); + kernel.GlobalScheduler().AdjustSchedulingOnAffinity(this, old_affinity_mask, old_core); } } return RESULT_SUCCESS; } -void Thread::AdjustSchedulingOnStatus(u32 old_flags) { - if (old_flags == scheduling_state) { - return; - } - - auto& scheduler = kernel.GlobalScheduler(); - if (static_cast(old_flags & static_cast(ThreadSchedMasks::LowMask)) == - ThreadSchedStatus::Runnable) { - // In this case the thread was running, now it's pausing/exitting - if (processor_id >= 0) { - scheduler.Unschedule(current_priority, static_cast(processor_id), this); - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (core != static_cast(processor_id) && ((affinity_mask >> core) & 1) != 0) { - scheduler.Unsuggest(current_priority, core, this); - } - } - } else if (GetSchedulingStatus() == ThreadSchedStatus::Runnable) { - // The thread is now set to running from being stopped - if (processor_id >= 0) { - scheduler.Schedule(current_priority, static_cast(processor_id), this); - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (core != static_cast(processor_id) && ((affinity_mask >> core) & 1) != 0) { - scheduler.Suggest(current_priority, core, this); - } - } - } - - scheduler.SetReselectionPending(); -} - -void Thread::AdjustSchedulingOnPriority(u32 old_priority) { - if (GetSchedulingStatus() != ThreadSchedStatus::Runnable) { - return; - } - auto& scheduler = kernel.GlobalScheduler(); - if (processor_id >= 0) { - scheduler.Unschedule(old_priority, static_cast(processor_id), this); - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (core != static_cast(processor_id) && ((affinity_mask >> core) & 1) != 0) { - scheduler.Unsuggest(old_priority, core, this); - } - } - - // Add thread to the new priority queues. - Thread* current_thread = GetCurrentThread(); - - if (processor_id >= 0) { - if (current_thread == this) { - scheduler.SchedulePrepend(current_priority, static_cast(processor_id), this); - } else { - scheduler.Schedule(current_priority, static_cast(processor_id), this); - } - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (core != static_cast(processor_id) && ((affinity_mask >> core) & 1) != 0) { - scheduler.Suggest(current_priority, core, this); - } - } - - scheduler.SetReselectionPending(); -} - -void Thread::AdjustSchedulingOnAffinity(u64 old_affinity_mask, s32 old_core) { - auto& scheduler = kernel.GlobalScheduler(); - if (GetSchedulingStatus() != ThreadSchedStatus::Runnable || - current_priority >= THREADPRIO_COUNT) { - return; - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (((old_affinity_mask >> core) & 1) != 0) { - if (core == static_cast(old_core)) { - scheduler.Unschedule(current_priority, core, this); - } else { - scheduler.Unsuggest(current_priority, core, this); - } - } - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (((affinity_mask >> core) & 1) != 0) { - if (core == static_cast(processor_id)) { - scheduler.Schedule(current_priority, core, this); - } else { - scheduler.Suggest(current_priority, core, this); - } - } - } - - scheduler.SetReselectionPending(); -} - //////////////////////////////////////////////////////////////////////////////////////////////////// /** diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 23fdef8a40..33d340b47f 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -9,23 +9,42 @@ #include #include "common/common_types.h" +#include "common/spin_lock.h" #include "core/arm/arm_interface.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/synchronization_object.h" #include "core/hle/result.h" +namespace Common { +class Fiber; +} + +namespace Core { +class System; +} + namespace Kernel { +class GlobalScheduler; class KernelCore; class Process; class Scheduler; enum ThreadPriority : u32 { - THREADPRIO_HIGHEST = 0, ///< Highest thread priority - THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps - THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps - THREADPRIO_LOWEST = 63, ///< Lowest thread priority - THREADPRIO_COUNT = 64, ///< Total number of possible thread priorities. + THREADPRIO_HIGHEST = 0, ///< Highest thread priority + THREADPRIO_MAX_CORE_MIGRATION = 2, ///< Highest priority for a core migration + THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps + THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps + THREADPRIO_LOWEST = 63, ///< Lowest thread priority + THREADPRIO_COUNT = 64, ///< Total number of possible thread priorities. +}; + +enum ThreadType : u32 { + THREADTYPE_USER = 0x1, + THREADTYPE_KERNEL = 0x2, + THREADTYPE_HLE = 0x4, + THREADTYPE_IDLE = 0x8, + THREADTYPE_SUSPEND = 0x10, }; enum ThreadProcessorId : s32 { @@ -111,22 +130,43 @@ public: std::function thread, std::shared_ptr object, std::size_t index)>; + /** + * Creates and returns a new thread. The new thread is immediately scheduled + * @param system The instance of the whole system + * @param name The friendly name desired for the thread + * @param entry_point The address at which the thread should start execution + * @param priority The thread's priority + * @param arg User data to pass to the thread + * @param processor_id The ID(s) of the processors on which the thread is desired to be run + * @param stack_top The address of the thread's stack top + * @param owner_process The parent process for the thread, if null, it's a kernel thread + * @return A shared pointer to the newly created thread + */ + static ResultVal> Create(Core::System& system, ThreadType type_flags, std::string name, + VAddr entry_point, u32 priority, u64 arg, + s32 processor_id, VAddr stack_top, + Process* owner_process); + /** * Creates and returns a new thread. The new thread is immediately scheduled - * @param kernel The kernel instance this thread will be created under. + * @param system The instance of the whole system * @param name The friendly name desired for the thread * @param entry_point The address at which the thread should start execution * @param priority The thread's priority * @param arg User data to pass to the thread * @param processor_id The ID(s) of the processors on which the thread is desired to be run * @param stack_top The address of the thread's stack top - * @param owner_process The parent process for the thread + * @param owner_process The parent process for the thread, if null, it's a kernel thread + * @param thread_start_func The function where the host context will start. + * @param thread_start_parameter The parameter which will passed to host context on init * @return A shared pointer to the newly created thread */ - static ResultVal> Create(KernelCore& kernel, std::string name, + static ResultVal> Create(Core::System& system, ThreadType type_flags, std::string name, VAddr entry_point, u32 priority, u64 arg, s32 processor_id, VAddr stack_top, - Process& owner_process); + Process* owner_process, + std::function&& thread_start_func, + void* thread_start_parameter); std::string GetName() const override { return name; @@ -192,7 +232,9 @@ public: } /// Resumes a thread from waiting - void ResumeFromWait(); + void /* deprecated */ ResumeFromWait(); + + void OnWakeUp(); /// Cancels a waiting operation that this thread may or may not be within. /// @@ -206,10 +248,10 @@ public: * Schedules an event to wake up the specified thread after the specified delay * @param nanoseconds The time this thread will be allowed to sleep for */ - void WakeAfterDelay(s64 nanoseconds); + void /* deprecated */ WakeAfterDelay(s64 nanoseconds); /// Cancel any outstanding wakeup events for this thread - void CancelWakeupTimer(); + void /* deprecated */ CancelWakeupTimer(); /** * Sets the result after the thread awakens (from svcWaitSynchronization) @@ -290,6 +332,12 @@ public: return context_64; } + bool IsHLEThread() const { + return (type & THREADTYPE_HLE) != 0; + } + + std::shared_ptr GetHostContext() const; + ThreadStatus GetStatus() const { return status; } @@ -467,16 +515,19 @@ public: } private: + friend class GlobalScheduler; + friend class Scheduler; + void SetSchedulingStatus(ThreadSchedStatus new_status); void SetCurrentPriority(u32 new_priority); ResultCode SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask); - void AdjustSchedulingOnStatus(u32 old_flags); - void AdjustSchedulingOnPriority(u32 old_priority); void AdjustSchedulingOnAffinity(u64 old_affinity_mask, s32 old_core); ThreadContext32 context_32{}; ThreadContext64 context_64{}; + Common::SpinLock context_guard{}; + std::shared_ptr host_context{}; u64 thread_id = 0; @@ -485,6 +536,8 @@ private: VAddr entry_point = 0; VAddr stack_top = 0; + ThreadType type; + /// Nominal thread priority, as set by the emulated application. /// The nominal priority is the thread priority without priority /// inheritance taken into account. diff --git a/src/core/hle/kernel/time_manager.cpp b/src/core/hle/kernel/time_manager.cpp index 21b2904689..0b8f0d993b 100644 --- a/src/core/hle/kernel/time_manager.cpp +++ b/src/core/hle/kernel/time_manager.cpp @@ -19,7 +19,7 @@ TimeManager::TimeManager(Core::System& system) : system{system} { Handle proper_handle = static_cast(thread_handle); std::shared_ptr thread = this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle); - thread->ResumeFromWait(); + thread->OnWakeUp(); }); } diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp index 1f2131ec84..cb35919e93 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.cpp +++ b/src/core/hle/service/hid/controllers/debug_pad.cpp @@ -23,7 +23,7 @@ void Controller_DebugPad::OnRelease() {} void Controller_DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) { - shared_memory.header.timestamp = core_timing.GetTicks(); + shared_memory.header.timestamp = core_timing.GetCPUTicks(); shared_memory.header.total_entry_count = 17; if (!IsControllerActivated()) { diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp index 6e990dd009..b7b7bfeae5 100644 --- a/src/core/hle/service/hid/controllers/gesture.cpp +++ b/src/core/hle/service/hid/controllers/gesture.cpp @@ -19,7 +19,7 @@ void Controller_Gesture::OnRelease() {} void Controller_Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) { - shared_memory.header.timestamp = core_timing.GetTicks(); + shared_memory.header.timestamp = core_timing.GetCPUTicks(); shared_memory.header.total_entry_count = 17; if (!IsControllerActivated()) { diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp index 9a8d354ba7..feae89525a 100644 --- a/src/core/hle/service/hid/controllers/keyboard.cpp +++ b/src/core/hle/service/hid/controllers/keyboard.cpp @@ -21,7 +21,7 @@ void Controller_Keyboard::OnRelease() {} void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) { - shared_memory.header.timestamp = core_timing.GetTicks(); + shared_memory.header.timestamp = core_timing.GetCPUTicks(); shared_memory.header.total_entry_count = 17; if (!IsControllerActivated()) { diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp index 93d88ea50d..ac40989c58 100644 --- a/src/core/hle/service/hid/controllers/mouse.cpp +++ b/src/core/hle/service/hid/controllers/mouse.cpp @@ -19,7 +19,7 @@ void Controller_Mouse::OnRelease() {} void Controller_Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) { - shared_memory.header.timestamp = core_timing.GetTicks(); + shared_memory.header.timestamp = core_timing.GetCPUTicks(); shared_memory.header.total_entry_count = 17; if (!IsControllerActivated()) { diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 6fbee7efa3..ef67ad6902 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -328,7 +328,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* const auto& last_entry = main_controller->npad[main_controller->common.last_entry_index]; - main_controller->common.timestamp = core_timing.GetTicks(); + main_controller->common.timestamp = core_timing.GetCPUTicks(); main_controller->common.last_entry_index = (main_controller->common.last_entry_index + 1) % 17; diff --git a/src/core/hle/service/hid/controllers/stubbed.cpp b/src/core/hle/service/hid/controllers/stubbed.cpp index 9e527d176d..e7483bfa22 100644 --- a/src/core/hle/service/hid/controllers/stubbed.cpp +++ b/src/core/hle/service/hid/controllers/stubbed.cpp @@ -23,7 +23,7 @@ void Controller_Stubbed::OnUpdate(const Core::Timing::CoreTiming& core_timing, u } CommonHeader header{}; - header.timestamp = core_timing.GetTicks(); + header.timestamp = core_timing.GetCPUTicks(); header.total_entry_count = 17; header.entry_count = 0; header.last_entry_index = 0; diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp index 1c6e55566a..e326f8f5c4 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.cpp +++ b/src/core/hle/service/hid/controllers/touchscreen.cpp @@ -22,7 +22,7 @@ void Controller_Touchscreen::OnRelease() {} void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) { - shared_memory.header.timestamp = core_timing.GetTicks(); + shared_memory.header.timestamp = core_timing.GetCPUTicks(); shared_memory.header.total_entry_count = 17; if (!IsControllerActivated()) { @@ -49,7 +49,7 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin touch_entry.diameter_x = Settings::values.touchscreen.diameter_x; touch_entry.diameter_y = Settings::values.touchscreen.diameter_y; touch_entry.rotation_angle = Settings::values.touchscreen.rotation_angle; - const u64 tick = core_timing.GetTicks(); + const u64 tick = core_timing.GetCPUTicks(); touch_entry.delta_time = tick - last_touch; last_touch = tick; touch_entry.finger = Settings::values.touchscreen.finger; diff --git a/src/core/hle/service/hid/controllers/xpad.cpp b/src/core/hle/service/hid/controllers/xpad.cpp index 27511b27b3..2503ef241e 100644 --- a/src/core/hle/service/hid/controllers/xpad.cpp +++ b/src/core/hle/service/hid/controllers/xpad.cpp @@ -20,7 +20,7 @@ void Controller_XPad::OnRelease() {} void Controller_XPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) { for (auto& xpad_entry : shared_memory.shared_memory_entries) { - xpad_entry.header.timestamp = core_timing.GetTicks(); + xpad_entry.header.timestamp = core_timing.GetCPUTicks(); xpad_entry.header.total_entry_count = 17; if (!IsControllerActivated()) { diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 57d5edea72..e9020e0dc7 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -39,11 +39,9 @@ namespace Service::HID { // Updating period for each HID device. // TODO(ogniK): Find actual polling rate of hid -constexpr s64 pad_update_ticks = static_cast(Core::Hardware::BASE_CLOCK_RATE / 66); -[[maybe_unused]] constexpr s64 accelerometer_update_ticks = - static_cast(Core::Hardware::BASE_CLOCK_RATE / 100); -[[maybe_unused]] constexpr s64 gyroscope_update_ticks = - static_cast(Core::Hardware::BASE_CLOCK_RATE / 100); +constexpr s64 pad_update_ticks = static_cast(1000000000 / 66); +[[maybe_unused]] constexpr s64 accelerometer_update_ticks = static_cast(1000000000 / 100); +[[maybe_unused]] constexpr s64 gyroscope_update_ticks = static_cast(1000000000 / 100); constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000; IAppletResource::IAppletResource(Core::System& system) @@ -78,8 +76,8 @@ IAppletResource::IAppletResource(Core::System& system) // Register update callbacks pad_update_event = - Core::Timing::CreateEvent("HID::UpdatePadCallback", [this](u64 userdata, s64 cycles_late) { - UpdateControllers(userdata, cycles_late); + Core::Timing::CreateEvent("HID::UpdatePadCallback", [this](u64 userdata, s64 ns_late) { + UpdateControllers(userdata, ns_late); }); // TODO(shinyquagsire23): Other update callbacks? (accel, gyro?) @@ -109,7 +107,7 @@ void IAppletResource::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) { rb.PushCopyObjects(shared_mem); } -void IAppletResource::UpdateControllers(u64 userdata, s64 cycles_late) { +void IAppletResource::UpdateControllers(u64 userdata, s64 ns_late) { auto& core_timing = system.CoreTiming(); const bool should_reload = Settings::values.is_device_reload_pending.exchange(false); @@ -120,7 +118,7 @@ void IAppletResource::UpdateControllers(u64 userdata, s64 cycles_late) { controller->OnUpdate(core_timing, shared_mem->GetPointer(), SHARED_MEMORY_SIZE); } - core_timing.ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event); + core_timing.ScheduleEvent(pad_update_ticks - ns_late, pad_update_event); } class IActiveVibrationDeviceList final : public ServiceFramework { diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index 36ed6f7da4..e82fd031b7 100644 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -98,7 +98,7 @@ void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 5}; rb.Push(RESULT_SUCCESS); - rb.PushRaw(system.CoreTiming().GetTicks()); + rb.PushRaw(system.CoreTiming().GetCPUTicks()); rb.PushRaw(0); } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index 0d913334e8..fba89e7a6e 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -200,8 +200,7 @@ u32 nvhost_ctrl_gpu::GetGpuTime(const std::vector& input, std::vector& o IoctlGetGpuTime params{}; std::memcpy(¶ms, input.data(), input.size()); - const auto ns = Core::Timing::CyclesToNs(system.CoreTiming().GetTicks()); - params.gpu_time = static_cast(ns.count()); + params.gpu_time = static_cast(system.CoreTiming().GetGlobalTimeNs().count()); std::memcpy(output.data(), ¶ms, output.size()); return 0; } diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 437bc5dee9..aaf28995db 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -27,8 +27,8 @@ namespace Service::NVFlinger { -constexpr s64 frame_ticks = static_cast(Core::Hardware::BASE_CLOCK_RATE / 60); -constexpr s64 frame_ticks_30fps = static_cast(Core::Hardware::BASE_CLOCK_RATE / 30); +constexpr s64 frame_ticks = static_cast(1000000000 / 60); +constexpr s64 frame_ticks_30fps = static_cast(1000000000 / 30); NVFlinger::NVFlinger(Core::System& system) : system(system) { displays.emplace_back(0, "Default", system); @@ -39,11 +39,10 @@ NVFlinger::NVFlinger(Core::System& system) : system(system) { // Schedule the screen composition events composition_event = - Core::Timing::CreateEvent("ScreenComposition", [this](u64 userdata, s64 cycles_late) { + Core::Timing::CreateEvent("ScreenComposition", [this](u64 userdata, s64 ns_late) { Compose(); - const auto ticks = - Settings::values.force_30fps_mode ? frame_ticks_30fps : GetNextTicks(); - this->system.CoreTiming().ScheduleEvent(std::max(0LL, ticks - cycles_late), + const auto ticks = GetNextTicks(); + this->system.CoreTiming().ScheduleEvent(std::max(0LL, ticks - ns_late), composition_event); }); @@ -223,7 +222,7 @@ void NVFlinger::Compose() { s64 NVFlinger::GetNextTicks() const { constexpr s64 max_hertz = 120LL; - return (Core::Hardware::BASE_CLOCK_RATE * (1LL << swap_interval)) / max_hertz; + return (1000000000 * (1LL << swap_interval)) / max_hertz; } } // namespace Service::NVFlinger diff --git a/src/core/hle/service/time/standard_steady_clock_core.cpp b/src/core/hle/service/time/standard_steady_clock_core.cpp index 1575f0b49d..59a272f4a8 100644 --- a/src/core/hle/service/time/standard_steady_clock_core.cpp +++ b/src/core/hle/service/time/standard_steady_clock_core.cpp @@ -11,9 +11,8 @@ namespace Service::Time::Clock { TimeSpanType StandardSteadyClockCore::GetCurrentRawTimePoint(Core::System& system) { - const TimeSpanType ticks_time_span{TimeSpanType::FromTicks( - Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), - Core::Hardware::CNTFREQ)}; + const TimeSpanType ticks_time_span{ + TimeSpanType::FromTicks(system.CoreTiming().GetClockTicks(), Core::Hardware::CNTFREQ)}; TimeSpanType raw_time_point{setup_value.nanoseconds + ticks_time_span.nanoseconds}; if (raw_time_point.nanoseconds < cached_raw_time_point.nanoseconds) { diff --git a/src/core/hle/service/time/tick_based_steady_clock_core.cpp b/src/core/hle/service/time/tick_based_steady_clock_core.cpp index 44d5bc651e..8baaa2a6af 100644 --- a/src/core/hle/service/time/tick_based_steady_clock_core.cpp +++ b/src/core/hle/service/time/tick_based_steady_clock_core.cpp @@ -11,9 +11,8 @@ namespace Service::Time::Clock { SteadyClockTimePoint TickBasedSteadyClockCore::GetTimePoint(Core::System& system) { - const TimeSpanType ticks_time_span{TimeSpanType::FromTicks( - Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), - Core::Hardware::CNTFREQ)}; + const TimeSpanType ticks_time_span{ + TimeSpanType::FromTicks(system.CoreTiming().GetClockTicks(), Core::Hardware::CNTFREQ)}; return {ticks_time_span.ToSeconds(), GetClockSourceId()}; } diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp index 67f1bbcf36..4cf58a61a2 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -234,9 +234,8 @@ void Module::Interface::CalculateMonotonicSystemClockBaseTimePoint(Kernel::HLERe const auto current_time_point{steady_clock_core.GetCurrentTimePoint(system)}; if (current_time_point.clock_source_id == context.steady_time_point.clock_source_id) { - const auto ticks{Clock::TimeSpanType::FromTicks( - Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), - Core::Hardware::CNTFREQ)}; + const auto ticks{Clock::TimeSpanType::FromTicks(system.CoreTiming().GetClockTicks(), + Core::Hardware::CNTFREQ)}; const s64 base_time_point{context.offset + current_time_point.time_point - ticks.ToSeconds()}; IPC::ResponseBuilder rb{ctx, (sizeof(s64) / 4) + 2}; diff --git a/src/core/hle/service/time/time_sharedmemory.cpp b/src/core/hle/service/time/time_sharedmemory.cpp index 999ec1e513..e0ae9f8748 100644 --- a/src/core/hle/service/time/time_sharedmemory.cpp +++ b/src/core/hle/service/time/time_sharedmemory.cpp @@ -30,8 +30,7 @@ void SharedMemory::SetupStandardSteadyClock(Core::System& system, const Common::UUID& clock_source_id, Clock::TimeSpanType current_time_point) { const Clock::TimeSpanType ticks_time_span{Clock::TimeSpanType::FromTicks( - Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), - Core::Hardware::CNTFREQ)}; + system.CoreTiming().GetClockTicks(), Core::Hardware::CNTFREQ)}; const Clock::SteadyClockContext context{ static_cast(current_time_point.nanoseconds - ticks_time_span.nanoseconds), clock_source_id}; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 9d87045a05..66634596d1 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -29,15 +29,12 @@ namespace Core::Memory { struct Memory::Impl { explicit Impl(Core::System& system_) : system{system_} {} - void SetCurrentPageTable(Kernel::Process& process) { + void SetCurrentPageTable(Kernel::Process& process, u32 core_id) { current_page_table = &process.PageTable().PageTableImpl(); const std::size_t address_space_width = process.PageTable().GetAddressSpaceWidth(); - system.ArmInterface(0).PageTableChanged(*current_page_table, address_space_width); - system.ArmInterface(1).PageTableChanged(*current_page_table, address_space_width); - system.ArmInterface(2).PageTableChanged(*current_page_table, address_space_width); - system.ArmInterface(3).PageTableChanged(*current_page_table, address_space_width); + system.ArmInterface(core_id).PageTableChanged(*current_page_table, address_space_width); } void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target) { @@ -689,8 +686,8 @@ struct Memory::Impl { Memory::Memory(Core::System& system) : impl{std::make_unique(system)} {} Memory::~Memory() = default; -void Memory::SetCurrentPageTable(Kernel::Process& process) { - impl->SetCurrentPageTable(process); +void Memory::SetCurrentPageTable(Kernel::Process& process, u32 core_id) { + impl->SetCurrentPageTable(process, core_id); } void Memory::MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target) { diff --git a/src/core/memory.h b/src/core/memory.h index 9292f3b0ae..93f0c1d6c4 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -64,7 +64,7 @@ public: * * @param process The process to use the page table of. */ - void SetCurrentPageTable(Kernel::Process& process); + void SetCurrentPageTable(Kernel::Process& process, u32 core_id); /** * Maps an allocated buffer onto a region of the emulated process address space. diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp index b139e84657..53d27859b9 100644 --- a/src/core/memory/cheat_engine.cpp +++ b/src/core/memory/cheat_engine.cpp @@ -20,7 +20,7 @@ namespace Core::Memory { -constexpr s64 CHEAT_ENGINE_TICKS = static_cast(Core::Hardware::BASE_CLOCK_RATE / 12); +constexpr s64 CHEAT_ENGINE_TICKS = static_cast(1000000000 / 12); constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; StandardVmCallbacks::StandardVmCallbacks(Core::System& system, const CheatProcessMetadata& metadata) @@ -190,7 +190,7 @@ CheatEngine::~CheatEngine() { void CheatEngine::Initialize() { event = Core::Timing::CreateEvent( "CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id), - [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); + [this](u64 userdata, s64 ns_late) { FrameCallback(userdata, ns_late); }); core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); metadata.process_id = system.CurrentProcess()->GetProcessID(); @@ -217,7 +217,7 @@ void CheatEngine::Reload(std::vector cheats) { MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); -void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) { +void CheatEngine::FrameCallback(u64 userdata, s64 ns_late) { if (is_pending_reload.exchange(false)) { vm.LoadProgram(cheats); } @@ -230,7 +230,7 @@ void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) { vm.Execute(metadata); - core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); + core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - ns_late, event); } } // namespace Core::Memory diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp index b2c6c537e8..8b0c50d111 100644 --- a/src/core/tools/freezer.cpp +++ b/src/core/tools/freezer.cpp @@ -14,7 +14,7 @@ namespace Tools { namespace { -constexpr s64 MEMORY_FREEZER_TICKS = static_cast(Core::Hardware::BASE_CLOCK_RATE / 60); +constexpr s64 MEMORY_FREEZER_TICKS = static_cast(1000000000 / 60); u64 MemoryReadWidth(Core::Memory::Memory& memory, u32 width, VAddr addr) { switch (width) { @@ -57,7 +57,7 @@ Freezer::Freezer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& m : core_timing{core_timing_}, memory{memory_} { event = Core::Timing::CreateEvent( "MemoryFreezer::FrameCallback", - [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); + [this](u64 userdata, s64 ns_late) { FrameCallback(userdata, ns_late); }); core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event); } @@ -158,7 +158,7 @@ std::vector Freezer::GetEntries() const { return entries; } -void Freezer::FrameCallback(u64 userdata, s64 cycles_late) { +void Freezer::FrameCallback(u64 userdata, s64 ns_late) { if (!IsActive()) { LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events."); return; @@ -173,7 +173,7 @@ void Freezer::FrameCallback(u64 userdata, s64 cycles_late) { MemoryWriteWidth(memory, entry.width, entry.address, entry.value); } - core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS - cycles_late, event); + core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS - ns_late, event); } void Freezer::FillEntryReads() { diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 3f750b51c9..47ef30aa91 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -8,7 +8,6 @@ add_executable(tests core/arm/arm_test_common.cpp core/arm/arm_test_common.h core/core_timing.cpp - core/host_timing.cpp tests.cpp ) diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp index ff2d11cc8e..795f3da099 100644 --- a/src/tests/core/core_timing.cpp +++ b/src/tests/core/core_timing.cpp @@ -16,31 +16,30 @@ namespace { // Numbers are chosen randomly to make sure the correct one is given. -constexpr std::array CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}}; -constexpr int MAX_SLICE_LENGTH = 10000; // Copied from CoreTiming internals +static constexpr std::array CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}}; +static constexpr int MAX_SLICE_LENGTH = 10000; // Copied from CoreTiming internals +static constexpr std::array calls_order{{2, 0, 1, 4, 3}}; +static std::array delays{}; std::bitset callbacks_ran_flags; u64 expected_callback = 0; s64 lateness = 0; template -void CallbackTemplate(u64 userdata, s64 cycles_late) { +void HostCallbackTemplate(u64 userdata, s64 nanoseconds_late) { static_assert(IDX < CB_IDS.size(), "IDX out of range"); callbacks_ran_flags.set(IDX); REQUIRE(CB_IDS[IDX] == userdata); - REQUIRE(CB_IDS[IDX] == expected_callback); - REQUIRE(lateness == cycles_late); + REQUIRE(CB_IDS[IDX] == CB_IDS[calls_order[expected_callback]]); + delays[IDX] = nanoseconds_late; + ++expected_callback; } u64 callbacks_done = 0; -void EmptyCallback(u64 userdata, s64 cycles_late) { - ++callbacks_done; -} - struct ScopeInit final { ScopeInit() { - core_timing.Initialize(); + core_timing.Initialize([]() {}); } ~ScopeInit() { core_timing.Shutdown(); @@ -49,110 +48,97 @@ struct ScopeInit final { Core::Timing::CoreTiming core_timing; }; -void AdvanceAndCheck(Core::Timing::CoreTiming& core_timing, u32 idx, u32 context = 0, - int expected_lateness = 0, int cpu_downcount = 0) { - callbacks_ran_flags = 0; - expected_callback = CB_IDS[idx]; - lateness = expected_lateness; - - // Pretend we executed X cycles of instructions. - core_timing.SwitchContext(context); - core_timing.AddTicks(core_timing.GetDowncount() - cpu_downcount); - core_timing.Advance(); - core_timing.SwitchContext((context + 1) % 4); - - REQUIRE(decltype(callbacks_ran_flags)().set(idx) == callbacks_ran_flags); -} -} // Anonymous namespace - TEST_CASE("CoreTiming[BasicOrder]", "[core]") { ScopeInit guard; auto& core_timing = guard.core_timing; + std::vector> events{ + Core::Timing::CreateEvent("callbackA", HostCallbackTemplate<0>), + Core::Timing::CreateEvent("callbackB", HostCallbackTemplate<1>), + Core::Timing::CreateEvent("callbackC", HostCallbackTemplate<2>), + Core::Timing::CreateEvent("callbackD", HostCallbackTemplate<3>), + Core::Timing::CreateEvent("callbackE", HostCallbackTemplate<4>), + }; - std::shared_ptr cb_a = - Core::Timing::CreateEvent("callbackA", CallbackTemplate<0>); - std::shared_ptr cb_b = - Core::Timing::CreateEvent("callbackB", CallbackTemplate<1>); - std::shared_ptr cb_c = - Core::Timing::CreateEvent("callbackC", CallbackTemplate<2>); - std::shared_ptr cb_d = - Core::Timing::CreateEvent("callbackD", CallbackTemplate<3>); - std::shared_ptr cb_e = - Core::Timing::CreateEvent("callbackE", CallbackTemplate<4>); + expected_callback = 0; - // Enter slice 0 - core_timing.ResetRun(); + core_timing.SyncPause(true); - // D -> B -> C -> A -> E - core_timing.SwitchContext(0); - core_timing.ScheduleEvent(1000, cb_a, CB_IDS[0]); - REQUIRE(1000 == core_timing.GetDowncount()); - core_timing.ScheduleEvent(500, cb_b, CB_IDS[1]); - REQUIRE(500 == core_timing.GetDowncount()); - core_timing.ScheduleEvent(800, cb_c, CB_IDS[2]); - REQUIRE(500 == core_timing.GetDowncount()); - core_timing.ScheduleEvent(100, cb_d, CB_IDS[3]); - REQUIRE(100 == core_timing.GetDowncount()); - core_timing.ScheduleEvent(1200, cb_e, CB_IDS[4]); - REQUIRE(100 == core_timing.GetDowncount()); + u64 one_micro = 1000U; + for (std::size_t i = 0; i < events.size(); i++) { + u64 order = calls_order[i]; + core_timing.ScheduleEvent(i * one_micro + 100U, events[order], CB_IDS[order]); + } + /// test pause + REQUIRE(callbacks_ran_flags.none()); - AdvanceAndCheck(core_timing, 3, 0); - AdvanceAndCheck(core_timing, 1, 1); - AdvanceAndCheck(core_timing, 2, 2); - AdvanceAndCheck(core_timing, 0, 3); - AdvanceAndCheck(core_timing, 4, 0); + core_timing.Pause(false); // No need to sync + + while (core_timing.HasPendingEvents()) + ; + + REQUIRE(callbacks_ran_flags.all()); + + for (std::size_t i = 0; i < delays.size(); i++) { + const double delay = static_cast(delays[i]); + const double micro = delay / 1000.0f; + const double mili = micro / 1000.0f; + printf("HostTimer Pausing Delay[%zu]: %.3f %.6f\n", i, micro, mili); + } } -TEST_CASE("CoreTiming[FairSharing]", "[core]") { +#pragma optimize("", off) +u64 TestTimerSpeed(Core::Timing::CoreTiming& core_timing) { + u64 start = core_timing.GetGlobalTimeNs().count(); + u64 placebo = 0; + for (std::size_t i = 0; i < 1000; i++) { + placebo += core_timing.GetGlobalTimeNs().count(); + } + u64 end = core_timing.GetGlobalTimeNs().count(); + return (end - start); +} +#pragma optimize("", on) +TEST_CASE("CoreTiming[BasicOrderNoPausing]", "[core]") { ScopeInit guard; auto& core_timing = guard.core_timing; + std::vector> events{ + Core::Timing::CreateEvent("callbackA", HostCallbackTemplate<0>), + Core::Timing::CreateEvent("callbackB", HostCallbackTemplate<1>), + Core::Timing::CreateEvent("callbackC", HostCallbackTemplate<2>), + Core::Timing::CreateEvent("callbackD", HostCallbackTemplate<3>), + Core::Timing::CreateEvent("callbackE", HostCallbackTemplate<4>), + }; - std::shared_ptr empty_callback = - Core::Timing::CreateEvent("empty_callback", EmptyCallback); + core_timing.SyncPause(true); + core_timing.SyncPause(false); - callbacks_done = 0; - u64 MAX_CALLBACKS = 10; - for (std::size_t i = 0; i < 10; i++) { - core_timing.ScheduleEvent(i * 3333U, empty_callback, 0); + expected_callback = 0; + + u64 start = core_timing.GetGlobalTimeNs().count(); + u64 one_micro = 1000U; + for (std::size_t i = 0; i < events.size(); i++) { + u64 order = calls_order[i]; + core_timing.ScheduleEvent(i * one_micro + 100U, events[order], CB_IDS[order]); + } + u64 end = core_timing.GetGlobalTimeNs().count(); + const double scheduling_time = static_cast(end - start); + const double timer_time = static_cast(TestTimerSpeed(core_timing)); + + while (core_timing.HasPendingEvents()) + ; + + REQUIRE(callbacks_ran_flags.all()); + + for (std::size_t i = 0; i < delays.size(); i++) { + const double delay = static_cast(delays[i]); + const double micro = delay / 1000.0f; + const double mili = micro / 1000.0f; + printf("HostTimer No Pausing Delay[%zu]: %.3f %.6f\n", i, micro, mili); } - const s64 advances = MAX_SLICE_LENGTH / 10; - core_timing.ResetRun(); - u64 current_time = core_timing.GetTicks(); - bool keep_running{}; - do { - keep_running = false; - for (u32 active_core = 0; active_core < 4; ++active_core) { - core_timing.SwitchContext(active_core); - if (core_timing.CanCurrentContextRun()) { - core_timing.AddTicks(std::min(advances, core_timing.GetDowncount())); - core_timing.Advance(); - } - keep_running |= core_timing.CanCurrentContextRun(); - } - } while (keep_running); - u64 current_time_2 = core_timing.GetTicks(); - - REQUIRE(MAX_CALLBACKS == callbacks_done); - REQUIRE(current_time_2 == current_time + MAX_SLICE_LENGTH * 4); -} - -TEST_CASE("Core::Timing[PredictableLateness]", "[core]") { - ScopeInit guard; - auto& core_timing = guard.core_timing; - - std::shared_ptr cb_a = - Core::Timing::CreateEvent("callbackA", CallbackTemplate<0>); - std::shared_ptr cb_b = - Core::Timing::CreateEvent("callbackB", CallbackTemplate<1>); - - // Enter slice 0 - core_timing.ResetRun(); - - core_timing.ScheduleEvent(100, cb_a, CB_IDS[0]); - core_timing.ScheduleEvent(200, cb_b, CB_IDS[1]); - - AdvanceAndCheck(core_timing, 0, 0, 10, -10); // (100 - 10) - AdvanceAndCheck(core_timing, 1, 1, 50, -50); + const double micro = scheduling_time / 1000.0f; + const double mili = micro / 1000.0f; + printf("HostTimer No Pausing Scheduling Time: %.3f %.6f\n", micro, mili); + printf("HostTimer No Pausing Timer Time: %.3f %.6f\n", timer_time / 1000.f, + timer_time / 1000000.f); } diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 8eb017f65d..482e497118 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include + #include "common/assert.h" #include "common/microprofile.h" #include "core/core.h" @@ -154,8 +156,7 @@ u64 GPU::GetTicks() const { constexpr u64 gpu_ticks_num = 384; constexpr u64 gpu_ticks_den = 625; - const u64 cpu_ticks = system.CoreTiming().GetTicks(); - u64 nanoseconds = Core::Timing::CyclesToNs(cpu_ticks).count(); + u64 nanoseconds = system.CoreTiming().GetGlobalTimeNs().count(); if (Settings::values.use_fast_gpu_time) { nanoseconds /= 256; } diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index bfeb164582..9ceb6c8d73 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -52,6 +52,8 @@ void EmuThread::run() { emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); + Core::System::GetInstance().RegisterHostThread(); + Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources( stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { emit LoadProgress(stage, value, total); @@ -65,28 +67,30 @@ void EmuThread::run() { bool was_active = false; while (!stop_run) { if (running) { - if (!was_active) + if (was_active) { emit DebugModeLeft(); + } - Core::System::ResultStatus result = Core::System::GetInstance().RunLoop(); + running_guard = true; + Core::System::ResultStatus result = Core::System::GetInstance().Run(); if (result != Core::System::ResultStatus::Success) { + running_guard = false; this->SetRunning(false); emit ErrorThrown(result, Core::System::GetInstance().GetStatusDetails()); } + running_wait.Wait(); + result = Core::System::GetInstance().Pause(); + if (result != Core::System::ResultStatus::Success) { + running_guard = false; + this->SetRunning(false); + emit ErrorThrown(result, Core::System::GetInstance().GetStatusDetails()); + } + running_guard = false; - was_active = running || exec_step; - if (!was_active && !stop_run) - emit DebugModeEntered(); - } else if (exec_step) { - if (!was_active) - emit DebugModeLeft(); - - exec_step = false; - Core::System::GetInstance().SingleStep(); + was_active = true; emit DebugModeEntered(); - yieldCurrentThread(); - - was_active = false; + } else if (exec_step) { + UNIMPLEMENTED(); } else { std::unique_lock lock{running_mutex}; running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; }); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 3626604cad..768568b3e9 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -59,6 +59,11 @@ public: this->running = running; lock.unlock(); running_cv.notify_all(); + if (!running) { + running_wait.Set(); + /// Wait until effectively paused + while (running_guard); + } } /** @@ -84,6 +89,8 @@ private: std::atomic_bool stop_run{false}; std::mutex running_mutex; std::condition_variable running_cv; + Common::Event running_wait{}; + std::atomic_bool running_guard{false}; signals: /** diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index c1ea25fb8d..765908c5a7 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -59,8 +59,10 @@ std::vector> WaitTreeItem::MakeThreadItemList() std::size_t row = 0; auto add_threads = [&](const std::vector>& threads) { for (std::size_t i = 0; i < threads.size(); ++i) { - item_list.push_back(std::make_unique(*threads[i])); - item_list.back()->row = row; + if (!threads[i]->IsHLEThread()) { + item_list.push_back(std::make_unique(*threads[i])); + item_list.back()->row = row; + } ++row; } }; diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 4d2ea7e9e9..1e5377840d 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -237,7 +237,7 @@ int main(int argc, char** argv) { std::thread render_thread([&emu_window] { emu_window->Present(); }); while (emu_window->IsOpen()) { - system.RunLoop(); + //system.RunLoop(); } render_thread.join(); diff --git a/src/yuzu_tester/yuzu.cpp b/src/yuzu_tester/yuzu.cpp index 676e70ebd7..1a45506d46 100644 --- a/src/yuzu_tester/yuzu.cpp +++ b/src/yuzu_tester/yuzu.cpp @@ -256,7 +256,7 @@ int main(int argc, char** argv) { system.Renderer().Rasterizer().LoadDiskResources(); while (!finished) { - system.RunLoop(); + //system.RunLoop(); } detached_tasks.WaitForAllTasks();