mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu-mainline.git
				synced 2025-03-21 01:53:15 +00:00 
			
		
		
		
	General: Recover Prometheus project from harddrive failure
This commit: Implements CPU Interrupts, Replaces Cycle Timing for Host Timing, Reworks the Kernel's Scheduler, Introduce Idle State and Suspended State, Recreates the bootmanager, Initializes Multicore system.
This commit is contained in:
		
							parent
							
								
									0ea4a8bcc4
								
							
						
					
					
						commit
						e31425df38
					
				| @ -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
 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
							
								
								
									
										29
									
								
								src/core/arm/cpu_interrupt_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/core/arm/cpu_interrupt_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -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<Common::Event>(); | ||||
| } | ||||
| 
 | ||||
| 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
 | ||||
							
								
								
									
										39
									
								
								src/core/arm/cpu_interrupt_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/core/arm/cpu_interrupt_handler.h
									
									
									
									
									
										Normal file
									
								
							| @ -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 <memory> | ||||
| 
 | ||||
| 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<Common::Event> interrupt_event; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Core
 | ||||
| @ -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<DynarmicCallbacks32>(*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<DynarmicCallbacks32>(*this)), | ||||
|       cp15(std::make_shared<DynarmicCP15>(*this)), core_index{core_index}, | ||||
|       exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {} | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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<u64>(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<DynarmicCallbacks64>(*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<DynarmicCallbacks64>(*this)), | ||||
|       inner_unicorn{system, interrupt_handler, ARM_Unicorn::Arch::AArch64}, core_index{core_index}, | ||||
|       exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {} | ||||
| 
 | ||||
| ARM_Dynarmic_64::~ARM_Dynarmic_64() = default; | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -6,6 +6,7 @@ | ||||
| #include <unicorn/arm64.h> | ||||
| #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); | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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<Core::DeviceMemory>(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<Impl>(*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(); | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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(); | ||||
| } | ||||
|  | ||||
| @ -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 <tuple> | ||||
| 
 | ||||
| #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<EventType> CreateEvent(std::string name, TimedCallback&& callback) { | ||||
|     return std::make_shared<EventType>(std::move(callback), std::move(name)); | ||||
| } | ||||
| 
 | ||||
| struct CoreTiming::Event { | ||||
|     s64 time; | ||||
|     u64 time; | ||||
|     u64 fifo_order; | ||||
|     u64 userdata; | ||||
|     std::weak_ptr<EventType> 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<void(void)>&& 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<std::thread>(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<EventType>& 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<EventType>& event_type, | ||||
|                                u64 userdata) { | ||||
|     basic_lock.lock(); | ||||
|     const u64 timeout = static_cast<u64>(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<EventType>& 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<EventType>& 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<u64>(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<u64>(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<s64>(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<EventType>& 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<EventType>& 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<s64>(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<int>(cycles); | ||||
| } | ||||
| 
 | ||||
| std::optional<u64> 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<std::mutex> guard(inner_mutex); | ||||
| 
 | ||||
|     const u64 cycles_executed = accumulated_ticks; | ||||
|     time_slice[current_context] = std::max<s64>(0, time_slice[current_context] - accumulated_ticks); | ||||
|     global_timer += cycles_executed; | ||||
| 
 | ||||
|     is_global_timer_sane = true; | ||||
| std::optional<u64> 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<s64>(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<s64>(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
 | ||||
|  | ||||
| @ -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 <atomic> | ||||
| #include <chrono> | ||||
| #include <functional> | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
| #include <optional> | ||||
| #include <string> | ||||
| #include <thread> | ||||
| #include <vector> | ||||
| 
 | ||||
| #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<void(void)>&& 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<EventType>& 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<EventType>& event_type, | ||||
|                        u64 userdata = 0); | ||||
| 
 | ||||
|     void UnscheduleEvent(const std::shared_ptr<EventType>& 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<EventType>& 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<u64> NextAvailableCore(const s64 needed_ticks) const; | ||||
|     /// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
 | ||||
|     std::optional<u64> 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<s64, num_cpu_cores> downcounts{}; | ||||
|     // Slice of time assigned to each core per run.
 | ||||
|     std::array<s64, num_cpu_cores> time_slice{}; | ||||
|     u64 current_context = 0; | ||||
|     std::unique_ptr<Common::WallClock> 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<EventType> ev_lost; | ||||
|     Common::Event event{}; | ||||
|     Common::SpinLock basic_lock{}; | ||||
|     Common::SpinLock advance_lock{}; | ||||
|     std::unique_ptr<std::thread> timer_thread; | ||||
|     std::atomic<bool> paused{}; | ||||
|     std::atomic<bool> paused_set{}; | ||||
|     std::atomic<bool> wait_set{}; | ||||
|     std::atomic<bool> shutting_down{}; | ||||
|     std::atomic<bool> has_started{}; | ||||
|     std::function<void(void)> on_thread_init{}; | ||||
| 
 | ||||
|     std::mutex inner_mutex; | ||||
|     std::array<std::atomic<u64>, Core::Hardware::NUM_CPU_CORES> ticks_count{}; | ||||
| }; | ||||
| 
 | ||||
| /// Creates a core timing event with the given name and callback.
 | ||||
|  | ||||
| @ -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<CoreManager>(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<std::thread>(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<CpuManager*>(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<CpuManager*>(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<CpuManager*>(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<void(void*)> CpuManager::GetGuestThreadStartFunc() { | ||||
|     return std::function<void(void*)>(GuestThreadFunction); | ||||
| } | ||||
| 
 | ||||
| void CpuManager::RunLoop(bool tight_loop) { | ||||
|     if (GDBStub::IsServerEnabled()) { | ||||
|         GDBStub::HandlePacket(); | ||||
| std::function<void(void*)> CpuManager::GetIdleThreadStartFunc() { | ||||
|     return std::function<void(void*)>(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<void(void*)> CpuManager::GetSuspendThreadStartFunc() { | ||||
|     return std::function<void(void*)>(SuspendThreadFunction); | ||||
| } | ||||
| 
 | ||||
| void* CpuManager::GetStartFuncParamater() { | ||||
|     return static_cast<void*>(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<Common::Event>(); | ||||
|     data.exit_barrier = std::make_unique<Common::Event>(); | ||||
|     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
 | ||||
|  | ||||
| @ -5,12 +5,18 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include <functional> | ||||
| #include <memory> | ||||
| #include <thread> | ||||
| #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<void(void*)> GetGuestThreadStartFunc(); | ||||
|     std::function<void(void*)> GetIdleThreadStartFunc(); | ||||
|     std::function<void(void*)> GetSuspendThreadStartFunc(); | ||||
|     void* GetStartFuncParamater(); | ||||
| 
 | ||||
| private: | ||||
|     std::array<std::unique_ptr<CoreManager>, 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<Common::Fiber> host_context; | ||||
|         std::unique_ptr<Common::Event> enter_barrier; | ||||
|         std::unique_ptr<Common::Event> exit_barrier; | ||||
|         std::atomic<bool> is_running; | ||||
|         std::atomic<bool> is_paused; | ||||
|         std::atomic<bool> initialized; | ||||
|         std::unique_ptr<std::thread> host_thread; | ||||
|     }; | ||||
| 
 | ||||
|     std::atomic<bool> running_mode{}; | ||||
|     std::atomic<bool> paused_state{}; | ||||
| 
 | ||||
|     std::array<CoreData, Core::Hardware::NUM_CPU_CORES> core_data{}; | ||||
| 
 | ||||
|     System& system; | ||||
| }; | ||||
|  | ||||
| @ -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<void(void*)> init_func = | ||||
|                 system.GetCpuManager().GetSuspendThreadStartFunc(); | ||||
|             void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater(); | ||||
|             ThreadType type = | ||||
|                 static_cast<ThreadType>(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_SUSPEND); | ||||
|             auto thread_res = Thread::Create(system, type, name, 0, 0, 0, static_cast<u32>(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<Kernel::SharedMemory> irs_shared_mem; | ||||
|     std::shared_ptr<Kernel::SharedMemory> time_shared_mem; | ||||
| 
 | ||||
|     std::array<std::shared_ptr<Thread>, 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
 | ||||
|  | ||||
| @ -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> impl; | ||||
|     bool exception_exited{}; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  | ||||
| @ -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<Core::ARM_Dynarmic_32>(system, exclusive_monitor, core_index); | ||||
|     arm_interface_64 = | ||||
|         std::make_unique<Core::ARM_Dynarmic_64>(system, exclusive_monitor, core_index); | ||||
| 
 | ||||
|     arm_interface_32 = std::make_unique<Core::ARM_Dynarmic_32>(system, interrupt_handler, | ||||
|                                                                exclusive_monitor, core_index); | ||||
|     arm_interface_64 = std::make_unique<Core::ARM_Dynarmic_64>(system, interrupt_handler, | ||||
|                                                                exclusive_monitor, core_index); | ||||
| #else | ||||
|     using Core::ARM_Unicorn; | ||||
|     arm_interface_32 = std::make_unique<ARM_Unicorn>(system, ARM_Unicorn::Arch::AArch32); | ||||
|     arm_interface_64 = std::make_unique<ARM_Unicorn>(system, ARM_Unicorn::Arch::AArch64); | ||||
|     arm_interface_32 = | ||||
|         std::make_unique<ARM_Unicorn>(system, interrupt_handler, ARM_Unicorn::Arch::AArch32); | ||||
|     arm_interface_64 = | ||||
|         std::make_unique<ARM_Unicorn>(system, interrupt_handler, ARM_Unicorn::Arch::AArch64); | ||||
|     LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); | ||||
| #endif | ||||
| 
 | ||||
|     scheduler = std::make_unique<Kernel::Scheduler>(system, core_index); | ||||
|     guard = std::make_unique<Common::SpinLock>(); | ||||
| } | ||||
| 
 | ||||
| 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
 | ||||
|  | ||||
| @ -7,6 +7,12 @@ | ||||
| #include <cstddef> | ||||
| #include <memory> | ||||
| 
 | ||||
| #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<Core::ARM_Interface> arm_interface_32; | ||||
|     std::unique_ptr<Core::ARM_Interface> arm_interface_64; | ||||
|     std::unique_ptr<Kernel::Scheduler> scheduler; | ||||
|     Core::ARM_Interface* arm_interface{}; | ||||
|     std::unique_ptr<Common::SpinLock> guard; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Kernel
 | ||||
|  | ||||
| @ -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> 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); | ||||
| } | ||||
|  | ||||
| @ -11,11 +11,15 @@ | ||||
| #include <utility> | ||||
| 
 | ||||
| #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> thread) { | ||||
|     global_list_guard.lock(); | ||||
|     thread_list.push_back(std::move(thread)); | ||||
|     global_list_guard.unlock(); | ||||
| } | ||||
| 
 | ||||
| void GlobalScheduler::RemoveThread(std::shared_ptr<Thread> 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<Thread*, Core::Hardware::NUM_CPU_CORES> 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<s32> 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<u32>(winner->GetProcessorID())); | ||||
|         } | ||||
|         TransferToCore(winner->GetPriority(), static_cast<s32>(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<u32>(src_core)); | ||||
| 
 | ||||
|     while (idle_cores != 0) { | ||||
|         u32 core_id = Common::CountTrailingZeroes32(idle_cores); | ||||
| 
 | ||||
|         if (!suggested_queue[core_id].empty()) { | ||||
|             std::array<s32, Core::Hardware::NUM_CPU_CORES> 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<s32>(core_id), suggested); | ||||
|                     break; | ||||
|                 } | ||||
|                 migration_candidates[num_candidates++] = suggested_core_id; | ||||
|             } | ||||
|             TransferToCore(thread_on_core->GetPriority(), static_cast<s32>(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<s32>(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<u32>(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<u32>(winner->GetProcessorID())); | ||||
|                 } | ||||
|                 TransferToCore(winner->GetPriority(), static_cast<s32>(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<u32>(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<u32>(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<ThreadSchedStatus>(old_flags & static_cast<u32>(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<u32>(thread->processor_id), thread); | ||||
|         } | ||||
| 
 | ||||
|         for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { | ||||
|             if (core != static_cast<u32>(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<u32>(thread->processor_id), thread); | ||||
|         } | ||||
| 
 | ||||
|         for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { | ||||
|             if (core != static_cast<u32>(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<u32>(thread->processor_id), thread); | ||||
|     } | ||||
| 
 | ||||
|     for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { | ||||
|         if (core != static_cast<u32>(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<u32>(thread->processor_id), | ||||
|                             thread); | ||||
|         } else { | ||||
|             Schedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { | ||||
|         if (core != static_cast<u32>(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<u32>(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<u32>(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<Common::Fiber> 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<Common::Fiber> 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<void(void*)> init_func = system.GetCpuManager().GetIdleThreadStartFunc(); | ||||
|     void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater(); | ||||
|     ThreadType type = static_cast<ThreadType>(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_IDLE); | ||||
|     auto thread_res = Thread::Create(system, type, name, 0, 64, 0, static_cast<u32>(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; | ||||
|  | ||||
| @ -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<s64> 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<std::shared_ptr<Thread>> 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<Thread> current_thread = nullptr; | ||||
|     std::shared_ptr<Thread> selected_thread = nullptr; | ||||
|     std::shared_ptr<Thread> 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; | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -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, | ||||
|                    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; | ||||
| } | ||||
|  | ||||
| @ -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<std::shared_ptr<Thread>> 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<Common::Fiber> Thread::GetHostContext() const { | ||||
|     return host_context; | ||||
| } | ||||
| 
 | ||||
| ResultVal<std::shared_ptr<Thread>> 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<void(void*)> 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<std::shared_ptr<Thread>> 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<void(void*)>&& 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<std::shared_ptr<Thread>> 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> thread = std::make_shared<Thread>(kernel); | ||||
| @ -183,7 +214,7 @@ ResultVal<std::shared_ptr<Thread>> 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<std::shared_ptr<Thread>> 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<u32>(stack_top), | ||||
|                          static_cast<u32>(entry_point), static_cast<u32>(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<u32>(stack_top), | ||||
|                              static_cast<u32>(entry_point), static_cast<u32>(arg)); | ||||
|         ResetThreadContext64(thread->context_64, stack_top, entry_point, arg); | ||||
|     } | ||||
|     thread->host_context = | ||||
|         std::make_shared<Common::Fiber>(std::move(thread_start_func), thread_start_parameter); | ||||
| 
 | ||||
|     return MakeResult<std::shared_ptr<Thread>>(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<u32>(ThreadSchedMasks::HighMask)) | | ||||
|                        static_cast<u32>(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<ThreadSchedStatus>(old_flags & static_cast<u32>(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<u32>(processor_id), this); | ||||
|         } | ||||
| 
 | ||||
|         for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { | ||||
|             if (core != static_cast<u32>(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<u32>(processor_id), this); | ||||
|         } | ||||
| 
 | ||||
|         for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { | ||||
|             if (core != static_cast<u32>(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<u32>(processor_id), this); | ||||
|     } | ||||
| 
 | ||||
|     for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { | ||||
|         if (core != static_cast<u32>(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<u32>(processor_id), this); | ||||
|         } else { | ||||
|             scheduler.Schedule(current_priority, static_cast<u32>(processor_id), this); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { | ||||
|         if (core != static_cast<u32>(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<u32>(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<u32>(processor_id)) { | ||||
|                 scheduler.Schedule(current_priority, core, this); | ||||
|             } else { | ||||
|                 scheduler.Suggest(current_priority, core, this); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     scheduler.SetReselectionPending(); | ||||
| } | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| /**
 | ||||
|  | ||||
| @ -9,23 +9,42 @@ | ||||
| #include <vector> | ||||
| 
 | ||||
| #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<bool(ThreadWakeupReason reason, std::shared_ptr<Thread> thread, | ||||
|                            std::shared_ptr<SynchronizationObject> 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<std::shared_ptr<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); | ||||
| 
 | ||||
|     /**
 | ||||
|      * 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<std::shared_ptr<Thread>> Create(KernelCore& kernel, std::string name, | ||||
|     static ResultVal<std::shared_ptr<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); | ||||
|                                                      Process* owner_process, | ||||
|                                                      std::function<void(void*)>&& 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<Common::Fiber> 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<Common::Fiber> 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.
 | ||||
|  | ||||
| @ -19,7 +19,7 @@ TimeManager::TimeManager(Core::System& system) : system{system} { | ||||
|             Handle proper_handle = static_cast<Handle>(thread_handle); | ||||
|             std::shared_ptr<Thread> thread = | ||||
|                 this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle); | ||||
|             thread->ResumeFromWait(); | ||||
|             thread->OnWakeUp(); | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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()) { | ||||
|  | ||||
| @ -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()) { | ||||
|  | ||||
| @ -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()) { | ||||
|  | ||||
| @ -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()) { | ||||
|  | ||||
| @ -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; | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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()) { | ||||
|  | ||||
| @ -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<s64>(Core::Hardware::BASE_CLOCK_RATE / 66); | ||||
| [[maybe_unused]] constexpr s64 accelerometer_update_ticks = | ||||
|     static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 100); | ||||
| [[maybe_unused]] constexpr s64 gyroscope_update_ticks = | ||||
|     static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 100); | ||||
| constexpr s64 pad_update_ticks = static_cast<s64>(1000000000 / 66); | ||||
| [[maybe_unused]] constexpr s64 accelerometer_update_ticks = static_cast<s64>(1000000000 / 100); | ||||
| [[maybe_unused]] constexpr s64 gyroscope_update_ticks = static_cast<s64>(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<IActiveVibrationDeviceList> { | ||||
|  | ||||
| @ -98,7 +98,7 @@ void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) { | ||||
| 
 | ||||
|     IPC::ResponseBuilder rb{ctx, 5}; | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.PushRaw<u64>(system.CoreTiming().GetTicks()); | ||||
|     rb.PushRaw<u64>(system.CoreTiming().GetCPUTicks()); | ||||
|     rb.PushRaw<u32>(0); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -200,8 +200,7 @@ u32 nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u8>& o | ||||
| 
 | ||||
|     IoctlGetGpuTime params{}; | ||||
|     std::memcpy(¶ms, input.data(), input.size()); | ||||
|     const auto ns = Core::Timing::CyclesToNs(system.CoreTiming().GetTicks()); | ||||
|     params.gpu_time = static_cast<u64_le>(ns.count()); | ||||
|     params.gpu_time = static_cast<u64_le>(system.CoreTiming().GetGlobalTimeNs().count()); | ||||
|     std::memcpy(output.data(), ¶ms, output.size()); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| @ -27,8 +27,8 @@ | ||||
| 
 | ||||
| namespace Service::NVFlinger { | ||||
| 
 | ||||
| constexpr s64 frame_ticks = static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 60); | ||||
| constexpr s64 frame_ticks_30fps = static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 30); | ||||
| constexpr s64 frame_ticks = static_cast<s64>(1000000000 / 60); | ||||
| constexpr s64 frame_ticks_30fps = static_cast<s64>(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<s64>(0LL, ticks - cycles_late), | ||||
|             const auto ticks = GetNextTicks(); | ||||
|             this->system.CoreTiming().ScheduleEvent(std::max<s64>(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
 | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -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()}; | ||||
| } | ||||
|  | ||||
| @ -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}; | ||||
|  | ||||
| @ -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<u64>(current_time_point.nanoseconds - ticks_time_span.nanoseconds), | ||||
|         clock_source_id}; | ||||
|  | ||||
| @ -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<Impl>(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) { | ||||
|  | ||||
| @ -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. | ||||
|  | ||||
| @ -20,7 +20,7 @@ | ||||
| 
 | ||||
| namespace Core::Memory { | ||||
| 
 | ||||
| constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 12); | ||||
| constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(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<CheatEntry> 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
 | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
| namespace Tools { | ||||
| namespace { | ||||
| 
 | ||||
| constexpr s64 MEMORY_FREEZER_TICKS = static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 60); | ||||
| constexpr s64 MEMORY_FREEZER_TICKS = static_cast<s64>(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::Entry> 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() { | ||||
|  | ||||
| @ -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 | ||||
| ) | ||||
| 
 | ||||
|  | ||||
| @ -16,31 +16,30 @@ | ||||
| 
 | ||||
| namespace { | ||||
| // Numbers are chosen randomly to make sure the correct one is given.
 | ||||
| constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}}; | ||||
| constexpr int MAX_SLICE_LENGTH = 10000; // Copied from CoreTiming internals
 | ||||
| static constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}}; | ||||
| static constexpr int MAX_SLICE_LENGTH = 10000; // Copied from CoreTiming internals
 | ||||
| static constexpr std::array<u64, 5> calls_order{{2, 0, 1, 4, 3}}; | ||||
| static std::array<s64, 5> delays{}; | ||||
| 
 | ||||
| std::bitset<CB_IDS.size()> callbacks_ran_flags; | ||||
| u64 expected_callback = 0; | ||||
| s64 lateness = 0; | ||||
| 
 | ||||
| template <unsigned int IDX> | ||||
| 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<std::shared_ptr<Core::Timing::EventType>> 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<Core::Timing::EventType> cb_a = | ||||
|         Core::Timing::CreateEvent("callbackA", CallbackTemplate<0>); | ||||
|     std::shared_ptr<Core::Timing::EventType> cb_b = | ||||
|         Core::Timing::CreateEvent("callbackB", CallbackTemplate<1>); | ||||
|     std::shared_ptr<Core::Timing::EventType> cb_c = | ||||
|         Core::Timing::CreateEvent("callbackC", CallbackTemplate<2>); | ||||
|     std::shared_ptr<Core::Timing::EventType> cb_d = | ||||
|         Core::Timing::CreateEvent("callbackD", CallbackTemplate<3>); | ||||
|     std::shared_ptr<Core::Timing::EventType> 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<double>(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<std::shared_ptr<Core::Timing::EventType>> 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<Core::Timing::EventType> 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<double>(end - start); | ||||
|     const double timer_time = static_cast<double>(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<double>(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<s64>(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<Core::Timing::EventType> cb_a = | ||||
|         Core::Timing::CreateEvent("callbackA", CallbackTemplate<0>); | ||||
|     std::shared_ptr<Core::Timing::EventType> 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); | ||||
| } | ||||
|  | ||||
| @ -2,6 +2,8 @@ | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <chrono> | ||||
| 
 | ||||
| #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; | ||||
|     } | ||||
|  | ||||
| @ -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; }); | ||||
|  | ||||
| @ -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: | ||||
|     /**
 | ||||
|  | ||||
| @ -59,8 +59,10 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() | ||||
|     std::size_t row = 0; | ||||
|     auto add_threads = [&](const std::vector<std::shared_ptr<Kernel::Thread>>& threads) { | ||||
|         for (std::size_t i = 0; i < threads.size(); ++i) { | ||||
|             item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i])); | ||||
|             item_list.back()->row = row; | ||||
|             if (!threads[i]->IsHLEThread()) { | ||||
|                 item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i])); | ||||
|                 item_list.back()->row = row; | ||||
|             } | ||||
|             ++row; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
| @ -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(); | ||||
| 
 | ||||
|  | ||||
| @ -256,7 +256,7 @@ int main(int argc, char** argv) { | ||||
|     system.Renderer().Rasterizer().LoadDiskResources(); | ||||
| 
 | ||||
|     while (!finished) { | ||||
|         system.RunLoop(); | ||||
|         //system.RunLoop();
 | ||||
|     } | ||||
| 
 | ||||
|     detached_tasks.WaitForAllTasks(); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Fernando Sahmkow
						Fernando Sahmkow