summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common/multi_level_queue.h7
-rw-r--r--src/core/core.cpp16
-rw-r--r--src/core/core.h10
-rw-r--r--src/core/core_cpu.cpp27
-rw-r--r--src/core/core_cpu.h6
-rw-r--r--src/core/cpu_core_manager.cpp1
-rw-r--r--src/core/gdbstub/gdbstub.cpp32
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp46
-rw-r--r--src/core/hle/kernel/kernel.cpp43
-rw-r--r--src/core/hle/kernel/kernel.h7
-rw-r--r--src/core/hle/kernel/mutex.cpp3
-rw-r--r--src/core/hle/kernel/process.cpp5
-rw-r--r--src/core/hle/kernel/scheduler.cpp570
-rw-r--r--src/core/hle/kernel/scheduler.h247
-rw-r--r--src/core/hle/kernel/svc.cpp99
-rw-r--r--src/core/hle/kernel/thread.cpp252
-rw-r--r--src/core/hle/kernel/thread.h74
-rw-r--r--src/core/hle/kernel/wait_object.cpp7
-rw-r--r--src/yuzu/debugger/wait_tree.cpp5
19 files changed, 1022 insertions, 435 deletions
diff --git a/src/common/multi_level_queue.h b/src/common/multi_level_queue.h
index 9cb448f56..50acfdbf2 100644
--- a/src/common/multi_level_queue.h
+++ b/src/common/multi_level_queue.h
@@ -304,6 +304,13 @@ public:
304 return levels[priority == Depth ? 63 : priority].back(); 304 return levels[priority == Depth ? 63 : priority].back();
305 } 305 }
306 306
307 void clear() {
308 used_priorities = 0;
309 for (std::size_t i = 0; i < Depth; i++) {
310 levels[i].clear();
311 }
312 }
313
307private: 314private:
308 using const_list_iterator = typename std::list<T>::const_iterator; 315 using const_list_iterator = typename std::list<T>::const_iterator;
309 316
diff --git a/src/core/core.cpp b/src/core/core.cpp
index b7b9259ec..eba17218a 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -409,6 +409,12 @@ void System::PrepareReschedule() {
409 CurrentCpuCore().PrepareReschedule(); 409 CurrentCpuCore().PrepareReschedule();
410} 410}
411 411
412void System::PrepareReschedule(const u32 core_index) {
413 if (core_index < GlobalScheduler().CpuCoresCount()) {
414 CpuCore(core_index).PrepareReschedule();
415 }
416}
417
412PerfStatsResults System::GetAndResetPerfStats() { 418PerfStatsResults System::GetAndResetPerfStats() {
413 return impl->GetAndResetPerfStats(); 419 return impl->GetAndResetPerfStats();
414} 420}
@@ -449,6 +455,16 @@ const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
449 return CpuCore(core_index).Scheduler(); 455 return CpuCore(core_index).Scheduler();
450} 456}
451 457
458/// Gets the global scheduler
459Kernel::GlobalScheduler& System::GlobalScheduler() {
460 return impl->kernel.GlobalScheduler();
461}
462
463/// Gets the global scheduler
464const Kernel::GlobalScheduler& System::GlobalScheduler() const {
465 return impl->kernel.GlobalScheduler();
466}
467
452Kernel::Process* System::CurrentProcess() { 468Kernel::Process* System::CurrentProcess() {
453 return impl->kernel.CurrentProcess(); 469 return impl->kernel.CurrentProcess();
454} 470}
diff --git a/src/core/core.h b/src/core/core.h
index 90e7ac607..984074ce3 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -24,6 +24,7 @@ class VfsFilesystem;
24} // namespace FileSys 24} // namespace FileSys
25 25
26namespace Kernel { 26namespace Kernel {
27class GlobalScheduler;
27class KernelCore; 28class KernelCore;
28class Process; 29class Process;
29class Scheduler; 30class Scheduler;
@@ -184,6 +185,9 @@ public:
184 /// Prepare the core emulation for a reschedule 185 /// Prepare the core emulation for a reschedule
185 void PrepareReschedule(); 186 void PrepareReschedule();
186 187
188 /// Prepare the core emulation for a reschedule
189 void PrepareReschedule(u32 core_index);
190
187 /// Gets and resets core performance statistics 191 /// Gets and resets core performance statistics
188 PerfStatsResults GetAndResetPerfStats(); 192 PerfStatsResults GetAndResetPerfStats();
189 193
@@ -238,6 +242,12 @@ public:
238 /// Gets the scheduler for the CPU core with the specified index 242 /// Gets the scheduler for the CPU core with the specified index
239 const Kernel::Scheduler& Scheduler(std::size_t core_index) const; 243 const Kernel::Scheduler& Scheduler(std::size_t core_index) const;
240 244
245 /// Gets the global scheduler
246 Kernel::GlobalScheduler& GlobalScheduler();
247
248 /// Gets the global scheduler
249 const Kernel::GlobalScheduler& GlobalScheduler() const;
250
241 /// Provides a pointer to the current process 251 /// Provides a pointer to the current process
242 Kernel::Process* CurrentProcess(); 252 Kernel::Process* CurrentProcess();
243 253
diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp
index 6bd9639c6..233ea572c 100644
--- a/src/core/core_cpu.cpp
+++ b/src/core/core_cpu.cpp
@@ -52,7 +52,8 @@ bool CpuBarrier::Rendezvous() {
52 52
53Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, 53Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
54 std::size_t core_index) 54 std::size_t core_index)
55 : cpu_barrier{cpu_barrier}, core_timing{system.CoreTiming()}, core_index{core_index} { 55 : cpu_barrier{cpu_barrier}, global_scheduler{system.GlobalScheduler()},
56 core_timing{system.CoreTiming()}, core_index{core_index} {
56#ifdef ARCHITECTURE_x86_64 57#ifdef ARCHITECTURE_x86_64
57 arm_interface = std::make_unique<ARM_Dynarmic>(system, exclusive_monitor, core_index); 58 arm_interface = std::make_unique<ARM_Dynarmic>(system, exclusive_monitor, core_index);
58#else 59#else
@@ -60,7 +61,7 @@ Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_ba
60 LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); 61 LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
61#endif 62#endif
62 63
63 scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface); 64 scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface, core_index);
64} 65}
65 66
66Cpu::~Cpu() = default; 67Cpu::~Cpu() = default;
@@ -81,21 +82,21 @@ void Cpu::RunLoop(bool tight_loop) {
81 return; 82 return;
82 } 83 }
83 84
85 Reschedule();
86
84 // If we don't have a currently active thread then don't execute instructions, 87 // If we don't have a currently active thread then don't execute instructions,
85 // instead advance to the next event and try to yield to the next thread 88 // instead advance to the next event and try to yield to the next thread
86 if (Kernel::GetCurrentThread() == nullptr) { 89 if (Kernel::GetCurrentThread() == nullptr) {
87 LOG_TRACE(Core, "Core-{} idling", core_index); 90 LOG_TRACE(Core, "Core-{} idling", core_index);
88 core_timing.Idle(); 91 core_timing.Idle();
89 core_timing.Advance();
90 PrepareReschedule();
91 } else { 92 } else {
92 if (tight_loop) { 93 if (tight_loop) {
93 arm_interface->Run(); 94 arm_interface->Run();
94 } else { 95 } else {
95 arm_interface->Step(); 96 arm_interface->Step();
96 } 97 }
97 core_timing.Advance();
98 } 98 }
99 core_timing.Advance();
99 100
100 Reschedule(); 101 Reschedule();
101} 102}
@@ -106,18 +107,18 @@ void Cpu::SingleStep() {
106 107
107void Cpu::PrepareReschedule() { 108void Cpu::PrepareReschedule() {
108 arm_interface->PrepareReschedule(); 109 arm_interface->PrepareReschedule();
109 reschedule_pending = true;
110} 110}
111 111
112void Cpu::Reschedule() { 112void Cpu::Reschedule() {
113 if (!reschedule_pending) {
114 return;
115 }
116
117 reschedule_pending = false;
118 // Lock the global kernel mutex when we manipulate the HLE state 113 // Lock the global kernel mutex when we manipulate the HLE state
119 std::lock_guard lock{HLE::g_hle_lock}; 114 std::lock_guard lock(HLE::g_hle_lock);
120 scheduler->Reschedule(); 115
116 global_scheduler.SelectThread(core_index);
117 scheduler->TryDoContextSwitch();
118}
119
120void Cpu::Shutdown() {
121 scheduler->Shutdown();
121} 122}
122 123
123} // namespace Core 124} // namespace Core
diff --git a/src/core/core_cpu.h b/src/core/core_cpu.h
index 7589beb8c..cafca8df7 100644
--- a/src/core/core_cpu.h
+++ b/src/core/core_cpu.h
@@ -12,8 +12,9 @@
12#include "common/common_types.h" 12#include "common/common_types.h"
13 13
14namespace Kernel { 14namespace Kernel {
15class GlobalScheduler;
15class Scheduler; 16class Scheduler;
16} 17} // namespace Kernel
17 18
18namespace Core { 19namespace Core {
19class System; 20class System;
@@ -83,6 +84,8 @@ public:
83 return core_index; 84 return core_index;
84 } 85 }
85 86
87 void Shutdown();
88
86 static std::unique_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores); 89 static std::unique_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores);
87 90
88private: 91private:
@@ -90,6 +93,7 @@ private:
90 93
91 std::unique_ptr<ARM_Interface> arm_interface; 94 std::unique_ptr<ARM_Interface> arm_interface;
92 CpuBarrier& cpu_barrier; 95 CpuBarrier& cpu_barrier;
96 Kernel::GlobalScheduler& global_scheduler;
93 std::unique_ptr<Kernel::Scheduler> scheduler; 97 std::unique_ptr<Kernel::Scheduler> scheduler;
94 Timing::CoreTiming& core_timing; 98 Timing::CoreTiming& core_timing;
95 99
diff --git a/src/core/cpu_core_manager.cpp b/src/core/cpu_core_manager.cpp
index 16b384076..8efd410bb 100644
--- a/src/core/cpu_core_manager.cpp
+++ b/src/core/cpu_core_manager.cpp
@@ -58,6 +58,7 @@ void CpuCoreManager::Shutdown() {
58 58
59 thread_to_cpu.clear(); 59 thread_to_cpu.clear();
60 for (auto& cpu_core : cores) { 60 for (auto& cpu_core : cores) {
61 cpu_core->Shutdown();
61 cpu_core.reset(); 62 cpu_core.reset();
62 } 63 }
63 64
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index db51d722f..20bb50868 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -202,13 +202,11 @@ void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) {
202} 202}
203 203
204static Kernel::Thread* FindThreadById(s64 id) { 204static Kernel::Thread* FindThreadById(s64 id) {
205 for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) { 205 const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList();
206 const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList(); 206 for (auto& thread : threads) {
207 for (auto& thread : threads) { 207 if (thread->GetThreadID() == static_cast<u64>(id)) {
208 if (thread->GetThreadID() == static_cast<u64>(id)) { 208 current_core = thread->GetProcessorID();
209 current_core = core; 209 return thread.get();
210 return thread.get();
211 }
212 } 210 }
213 } 211 }
214 return nullptr; 212 return nullptr;
@@ -647,11 +645,9 @@ static void HandleQuery() {
647 SendReply(buffer.c_str()); 645 SendReply(buffer.c_str());
648 } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) { 646 } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
649 std::string val = "m"; 647 std::string val = "m";
650 for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) { 648 const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList();
651 const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList(); 649 for (const auto& thread : threads) {
652 for (const auto& thread : threads) { 650 val += fmt::format("{:x},", thread->GetThreadID());
653 val += fmt::format("{:x},", thread->GetThreadID());
654 }
655 } 651 }
656 val.pop_back(); 652 val.pop_back();
657 SendReply(val.c_str()); 653 SendReply(val.c_str());
@@ -661,13 +657,11 @@ static void HandleQuery() {
661 std::string buffer; 657 std::string buffer;
662 buffer += "l<?xml version=\"1.0\"?>"; 658 buffer += "l<?xml version=\"1.0\"?>";
663 buffer += "<threads>"; 659 buffer += "<threads>";
664 for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) { 660 const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList();
665 const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList(); 661 for (const auto& thread : threads) {
666 for (const auto& thread : threads) { 662 buffer +=
667 buffer += 663 fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",
668 fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*", 664 thread->GetThreadID(), thread->GetProcessorID(), thread->GetThreadID());
669 thread->GetThreadID(), core, thread->GetThreadID());
670 }
671 } 665 }
672 buffer += "</threads>"; 666 buffer += "</threads>";
673 SendReply(buffer.c_str()); 667 SendReply(buffer.c_str());
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
index c8842410b..de0a9064e 100644
--- a/src/core/hle/kernel/address_arbiter.cpp
+++ b/src/core/hle/kernel/address_arbiter.cpp
@@ -22,6 +22,7 @@ namespace Kernel {
22namespace { 22namespace {
23// Wake up num_to_wake (or all) threads in a vector. 23// Wake up num_to_wake (or all) threads in a vector.
24void WakeThreads(const std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_to_wake) { 24void WakeThreads(const std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_to_wake) {
25 auto& system = Core::System::GetInstance();
25 // Only process up to 'target' threads, unless 'target' is <= 0, in which case process 26 // Only process up to 'target' threads, unless 'target' is <= 0, in which case process
26 // them all. 27 // them all.
27 std::size_t last = waiting_threads.size(); 28 std::size_t last = waiting_threads.size();
@@ -35,6 +36,7 @@ void WakeThreads(const std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_
35 waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS); 36 waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
36 waiting_threads[i]->SetArbiterWaitAddress(0); 37 waiting_threads[i]->SetArbiterWaitAddress(0);
37 waiting_threads[i]->ResumeFromWait(); 38 waiting_threads[i]->ResumeFromWait();
39 system.PrepareReschedule(waiting_threads[i]->GetProcessorID());
38 } 40 }
39} 41}
40} // Anonymous namespace 42} // Anonymous namespace
@@ -89,12 +91,20 @@ ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr a
89 91
90 // Determine the modified value depending on the waiting count. 92 // Determine the modified value depending on the waiting count.
91 s32 updated_value; 93 s32 updated_value;
92 if (waiting_threads.empty()) { 94 if (num_to_wake <= 0) {
93 updated_value = value + 1; 95 if (waiting_threads.empty()) {
94 } else if (num_to_wake <= 0 || waiting_threads.size() <= static_cast<u32>(num_to_wake)) { 96 updated_value = value + 1;
95 updated_value = value - 1; 97 } else {
98 updated_value = value - 1;
99 }
96 } else { 100 } else {
97 updated_value = value; 101 if (waiting_threads.empty()) {
102 updated_value = value + 1;
103 } else if (waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
104 updated_value = value - 1;
105 } else {
106 updated_value = value;
107 }
98 } 108 }
99 109
100 if (static_cast<s32>(Memory::Read32(address)) != value) { 110 if (static_cast<s32>(Memory::Read32(address)) != value) {
@@ -169,30 +179,22 @@ ResultCode AddressArbiter::WaitForAddressImpl(VAddr address, s64 timeout) {
169 179
170 current_thread->WakeAfterDelay(timeout); 180 current_thread->WakeAfterDelay(timeout);
171 181
172 system.CpuCore(current_thread->GetProcessorID()).PrepareReschedule(); 182 system.PrepareReschedule(current_thread->GetProcessorID());
173 return RESULT_TIMEOUT; 183 return RESULT_TIMEOUT;
174} 184}
175 185
176std::vector<SharedPtr<Thread>> AddressArbiter::GetThreadsWaitingOnAddress(VAddr address) const { 186std::vector<SharedPtr<Thread>> AddressArbiter::GetThreadsWaitingOnAddress(VAddr address) const {
177 const auto RetrieveWaitingThreads = [this](std::size_t core_index,
178 std::vector<SharedPtr<Thread>>& waiting_threads,
179 VAddr arb_addr) {
180 const auto& scheduler = system.Scheduler(core_index);
181 const auto& thread_list = scheduler.GetThreadList();
182
183 for (const auto& thread : thread_list) {
184 if (thread->GetArbiterWaitAddress() == arb_addr) {
185 waiting_threads.push_back(thread);
186 }
187 }
188 };
189 187
190 // Retrieve all threads that are waiting for this address. 188 // Retrieve all threads that are waiting for this address.
191 std::vector<SharedPtr<Thread>> threads; 189 std::vector<SharedPtr<Thread>> threads;
192 RetrieveWaitingThreads(0, threads, address); 190 const auto& scheduler = system.GlobalScheduler();
193 RetrieveWaitingThreads(1, threads, address); 191 const auto& thread_list = scheduler.GetThreadList();
194 RetrieveWaitingThreads(2, threads, address); 192
195 RetrieveWaitingThreads(3, threads, address); 193 for (const auto& thread : thread_list) {
194 if (thread->GetArbiterWaitAddress() == address) {
195 threads.push_back(thread);
196 }
197 }
196 198
197 // Sort them by priority, such that the highest priority ones come first. 199 // Sort them by priority, such that the highest priority ones come first.
198 std::sort(threads.begin(), threads.end(), 200 std::sort(threads.begin(), threads.end(),
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 799e5e0d8..f94ac150d 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -12,12 +12,15 @@
12 12
13#include "core/core.h" 13#include "core/core.h"
14#include "core/core_timing.h" 14#include "core/core_timing.h"
15#include "core/core_timing_util.h"
15#include "core/hle/kernel/address_arbiter.h" 16#include "core/hle/kernel/address_arbiter.h"
16#include "core/hle/kernel/client_port.h" 17#include "core/hle/kernel/client_port.h"
18#include "core/hle/kernel/errors.h"
17#include "core/hle/kernel/handle_table.h" 19#include "core/hle/kernel/handle_table.h"
18#include "core/hle/kernel/kernel.h" 20#include "core/hle/kernel/kernel.h"
19#include "core/hle/kernel/process.h" 21#include "core/hle/kernel/process.h"
20#include "core/hle/kernel/resource_limit.h" 22#include "core/hle/kernel/resource_limit.h"
23#include "core/hle/kernel/scheduler.h"
21#include "core/hle/kernel/thread.h" 24#include "core/hle/kernel/thread.h"
22#include "core/hle/lock.h" 25#include "core/hle/lock.h"
23#include "core/hle/result.h" 26#include "core/hle/result.h"
@@ -58,12 +61,8 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] s64 cycles_
58 if (thread->HasWakeupCallback()) { 61 if (thread->HasWakeupCallback()) {
59 resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Timeout, thread, nullptr, 0); 62 resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
60 } 63 }
61 } 64 } else if (thread->GetStatus() == ThreadStatus::WaitMutex ||
62 65 thread->GetStatus() == ThreadStatus::WaitCondVar) {
63 if (thread->GetMutexWaitAddress() != 0 || thread->GetCondVarWaitAddress() != 0 ||
64 thread->GetWaitHandle() != 0) {
65 ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex ||
66 thread->GetStatus() == ThreadStatus::WaitCondVar);
67 thread->SetMutexWaitAddress(0); 66 thread->SetMutexWaitAddress(0);
68 thread->SetCondVarWaitAddress(0); 67 thread->SetCondVarWaitAddress(0);
69 thread->SetWaitHandle(0); 68 thread->SetWaitHandle(0);
@@ -83,18 +82,23 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] s64 cycles_
83 } 82 }
84 83
85 if (resume) { 84 if (resume) {
85 if (thread->GetStatus() == ThreadStatus::WaitCondVar ||
86 thread->GetStatus() == ThreadStatus::WaitArb) {
87 thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
88 }
86 thread->ResumeFromWait(); 89 thread->ResumeFromWait();
87 } 90 }
88} 91}
89 92
90struct KernelCore::Impl { 93struct KernelCore::Impl {
91 explicit Impl(Core::System& system) : system{system} {} 94 explicit Impl(Core::System& system) : system{system}, global_scheduler{system} {}
92 95
93 void Initialize(KernelCore& kernel) { 96 void Initialize(KernelCore& kernel) {
94 Shutdown(); 97 Shutdown();
95 98
96 InitializeSystemResourceLimit(kernel); 99 InitializeSystemResourceLimit(kernel);
97 InitializeThreads(); 100 InitializeThreads();
101 InitializePreemption();
98 } 102 }
99 103
100 void Shutdown() { 104 void Shutdown() {
@@ -110,6 +114,9 @@ struct KernelCore::Impl {
110 114
111 thread_wakeup_callback_handle_table.Clear(); 115 thread_wakeup_callback_handle_table.Clear();
112 thread_wakeup_event_type = nullptr; 116 thread_wakeup_event_type = nullptr;
117 preemption_event = nullptr;
118
119 global_scheduler.Shutdown();
113 120
114 named_ports.clear(); 121 named_ports.clear();
115 } 122 }
@@ -132,6 +139,18 @@ struct KernelCore::Impl {
132 system.CoreTiming().RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback); 139 system.CoreTiming().RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback);
133 } 140 }
134 141
142 void InitializePreemption() {
143 preemption_event = system.CoreTiming().RegisterEvent(
144 "PreemptionCallback", [this](u64 userdata, s64 cycles_late) {
145 global_scheduler.PreemptThreads();
146 s64 time_interval = Core::Timing::msToCycles(std::chrono::milliseconds(10));
147 system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
148 });
149
150 s64 time_interval = Core::Timing::msToCycles(std::chrono::milliseconds(10));
151 system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
152 }
153
135 std::atomic<u32> next_object_id{0}; 154 std::atomic<u32> next_object_id{0};
136 std::atomic<u64> next_kernel_process_id{Process::InitialKIPIDMin}; 155 std::atomic<u64> next_kernel_process_id{Process::InitialKIPIDMin};
137 std::atomic<u64> next_user_process_id{Process::ProcessIDMin}; 156 std::atomic<u64> next_user_process_id{Process::ProcessIDMin};
@@ -140,10 +159,12 @@ struct KernelCore::Impl {
140 // Lists all processes that exist in the current session. 159 // Lists all processes that exist in the current session.
141 std::vector<SharedPtr<Process>> process_list; 160 std::vector<SharedPtr<Process>> process_list;
142 Process* current_process = nullptr; 161 Process* current_process = nullptr;
162 Kernel::GlobalScheduler global_scheduler;
143 163
144 SharedPtr<ResourceLimit> system_resource_limit; 164 SharedPtr<ResourceLimit> system_resource_limit;
145 165
146 Core::Timing::EventType* thread_wakeup_event_type = nullptr; 166 Core::Timing::EventType* thread_wakeup_event_type = nullptr;
167 Core::Timing::EventType* preemption_event = nullptr;
147 // TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future, 168 // TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future,
148 // allowing us to simply use a pool index or similar. 169 // allowing us to simply use a pool index or similar.
149 Kernel::HandleTable thread_wakeup_callback_handle_table; 170 Kernel::HandleTable thread_wakeup_callback_handle_table;
@@ -203,6 +224,14 @@ const std::vector<SharedPtr<Process>>& KernelCore::GetProcessList() const {
203 return impl->process_list; 224 return impl->process_list;
204} 225}
205 226
227Kernel::GlobalScheduler& KernelCore::GlobalScheduler() {
228 return impl->global_scheduler;
229}
230
231const Kernel::GlobalScheduler& KernelCore::GlobalScheduler() const {
232 return impl->global_scheduler;
233}
234
206void KernelCore::AddNamedPort(std::string name, SharedPtr<ClientPort> port) { 235void KernelCore::AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
207 impl->named_ports.emplace(std::move(name), std::move(port)); 236 impl->named_ports.emplace(std::move(name), std::move(port));
208} 237}
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 0cc44ee76..c4397fc77 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -21,6 +21,7 @@ namespace Kernel {
21 21
22class AddressArbiter; 22class AddressArbiter;
23class ClientPort; 23class ClientPort;
24class GlobalScheduler;
24class HandleTable; 25class HandleTable;
25class Process; 26class Process;
26class ResourceLimit; 27class ResourceLimit;
@@ -75,6 +76,12 @@ public:
75 /// Retrieves the list of processes. 76 /// Retrieves the list of processes.
76 const std::vector<SharedPtr<Process>>& GetProcessList() const; 77 const std::vector<SharedPtr<Process>>& GetProcessList() const;
77 78
79 /// Gets the sole instance of the global scheduler
80 Kernel::GlobalScheduler& GlobalScheduler();
81
82 /// Gets the sole instance of the global scheduler
83 const Kernel::GlobalScheduler& GlobalScheduler() const;
84
78 /// Adds a port to the named port table 85 /// Adds a port to the named port table
79 void AddNamedPort(std::string name, SharedPtr<ClientPort> port); 86 void AddNamedPort(std::string name, SharedPtr<ClientPort> port);
80 87
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp
index 98e87313b..663d0f4b6 100644
--- a/src/core/hle/kernel/mutex.cpp
+++ b/src/core/hle/kernel/mutex.cpp
@@ -139,6 +139,9 @@ ResultCode Mutex::Release(VAddr address) {
139 thread->SetCondVarWaitAddress(0); 139 thread->SetCondVarWaitAddress(0);
140 thread->SetMutexWaitAddress(0); 140 thread->SetMutexWaitAddress(0);
141 thread->SetWaitHandle(0); 141 thread->SetWaitHandle(0);
142 thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
143
144 system.PrepareReschedule();
142 145
143 return RESULT_SUCCESS; 146 return RESULT_SUCCESS;
144} 147}
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index e80a12ac3..12a900bcc 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -213,10 +213,7 @@ void Process::PrepareForTermination() {
213 } 213 }
214 }; 214 };
215 215
216 stop_threads(system.Scheduler(0).GetThreadList()); 216 stop_threads(system.GlobalScheduler().GetThreadList());
217 stop_threads(system.Scheduler(1).GetThreadList());
218 stop_threads(system.Scheduler(2).GetThreadList());
219 stop_threads(system.Scheduler(3).GetThreadList());
220 217
221 FreeTLSRegion(tls_region_address); 218 FreeTLSRegion(tls_region_address);
222 tls_region_address = 0; 219 tls_region_address = 0;
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index e8447b69a..e6dcb9639 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -1,8 +1,13 @@
1// Copyright 2018 yuzu emulator team 1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4//
5// SelectThreads, Yield functions originally by TuxSH.
6// licensed under GPLv2 or later under exception provided by the author.
4 7
5#include <algorithm> 8#include <algorithm>
9#include <set>
10#include <unordered_set>
6#include <utility> 11#include <utility>
7 12
8#include "common/assert.h" 13#include "common/assert.h"
@@ -17,56 +22,434 @@
17 22
18namespace Kernel { 23namespace Kernel {
19 24
20std::mutex Scheduler::scheduler_mutex; 25GlobalScheduler::GlobalScheduler(Core::System& system) : system{system} {
26 is_reselection_pending = false;
27}
28
29void GlobalScheduler::AddThread(SharedPtr<Thread> thread) {
30 thread_list.push_back(std::move(thread));
31}
32
33void GlobalScheduler::RemoveThread(const Thread* thread) {
34 thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
35 thread_list.end());
36}
37
38/*
39 * UnloadThread selects a core and forces it to unload its current thread's context
40 */
41void GlobalScheduler::UnloadThread(s32 core) {
42 Scheduler& sched = system.Scheduler(core);
43 sched.UnloadThread();
44}
45
46/*
47 * SelectThread takes care of selecting the new scheduled thread.
48 * It does it in 3 steps:
49 * - First a thread is selected from the top of the priority queue. If no thread
50 * is obtained then we move to step two, else we are done.
51 * - Second we try to get a suggested thread that's not assigned to any core or
52 * that is not the top thread in that core.
53 * - Third is no suggested thread is found, we do a second pass and pick a running
54 * thread in another core and swap it with its current thread.
55 */
56void GlobalScheduler::SelectThread(u32 core) {
57 const auto update_thread = [](Thread* thread, Scheduler& sched) {
58 if (thread != sched.selected_thread) {
59 if (thread == nullptr) {
60 ++sched.idle_selection_count;
61 }
62 sched.selected_thread = thread;
63 }
64 sched.is_context_switch_pending = sched.selected_thread != sched.current_thread;
65 std::atomic_thread_fence(std::memory_order_seq_cst);
66 };
67 Scheduler& sched = system.Scheduler(core);
68 Thread* current_thread = nullptr;
69 // Step 1: Get top thread in schedule queue.
70 current_thread = scheduled_queue[core].empty() ? nullptr : scheduled_queue[core].front();
71 if (current_thread) {
72 update_thread(current_thread, sched);
73 return;
74 }
75 // Step 2: Try selecting a suggested thread.
76 Thread* winner = nullptr;
77 std::set<s32> sug_cores;
78 for (auto thread : suggested_queue[core]) {
79 s32 this_core = thread->GetProcessorID();
80 Thread* thread_on_core = nullptr;
81 if (this_core >= 0) {
82 thread_on_core = scheduled_queue[this_core].front();
83 }
84 if (this_core < 0 || thread != thread_on_core) {
85 winner = thread;
86 break;
87 }
88 sug_cores.insert(this_core);
89 }
90 // if we got a suggested thread, select it, else do a second pass.
91 if (winner && winner->GetPriority() > 2) {
92 if (winner->IsRunning()) {
93 UnloadThread(winner->GetProcessorID());
94 }
95 TransferToCore(winner->GetPriority(), core, winner);
96 update_thread(winner, sched);
97 return;
98 }
99 // Step 3: Select a suggested thread from another core
100 for (auto& src_core : sug_cores) {
101 auto it = scheduled_queue[src_core].begin();
102 it++;
103 if (it != scheduled_queue[src_core].end()) {
104 Thread* thread_on_core = scheduled_queue[src_core].front();
105 Thread* to_change = *it;
106 if (thread_on_core->IsRunning() || to_change->IsRunning()) {
107 UnloadThread(src_core);
108 }
109 TransferToCore(thread_on_core->GetPriority(), core, thread_on_core);
110 current_thread = thread_on_core;
111 break;
112 }
113 }
114 update_thread(current_thread, sched);
115}
116
117/*
118 * YieldThread takes a thread and moves it to the back of the it's priority list
119 * This operation can be redundant and no scheduling is changed if marked as so.
120 */
121bool GlobalScheduler::YieldThread(Thread* yielding_thread) {
122 // Note: caller should use critical section, etc.
123 const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
124 const u32 priority = yielding_thread->GetPriority();
125
126 // Yield the thread
127 ASSERT_MSG(yielding_thread == scheduled_queue[core_id].front(priority),
128 "Thread yielding without being in front");
129 scheduled_queue[core_id].yield(priority);
130
131 Thread* winner = scheduled_queue[core_id].front(priority);
132 return AskForReselectionOrMarkRedundant(yielding_thread, winner);
133}
134
135/*
136 * YieldThreadAndBalanceLoad takes a thread and moves it to the back of the it's priority list.
137 * Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or
138 * a better priority than the next thread in the core.
139 * This operation can be redundant and no scheduling is changed if marked as so.
140 */
141bool GlobalScheduler::YieldThreadAndBalanceLoad(Thread* yielding_thread) {
142 // Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section,
143 // etc.
144 const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
145 const u32 priority = yielding_thread->GetPriority();
146
147 // Yield the thread
148 ASSERT_MSG(yielding_thread == scheduled_queue[core_id].front(priority),
149 "Thread yielding without being in front");
150 scheduled_queue[core_id].yield(priority);
151
152 std::array<Thread*, NUM_CPU_CORES> current_threads;
153 for (u32 i = 0; i < NUM_CPU_CORES; i++) {
154 current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front();
155 }
156
157 Thread* next_thread = scheduled_queue[core_id].front(priority);
158 Thread* winner = nullptr;
159 for (auto& thread : suggested_queue[core_id]) {
160 const s32 source_core = thread->GetProcessorID();
161 if (source_core >= 0) {
162 if (current_threads[source_core] != nullptr) {
163 if (thread == current_threads[source_core] ||
164 current_threads[source_core]->GetPriority() < min_regular_priority) {
165 continue;
166 }
167 }
168 }
169 if (next_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks() ||
170 next_thread->GetPriority() < thread->GetPriority()) {
171 if (thread->GetPriority() <= priority) {
172 winner = thread;
173 break;
174 }
175 }
176 }
177
178 if (winner != nullptr) {
179 if (winner != yielding_thread) {
180 if (winner->IsRunning()) {
181 UnloadThread(winner->GetProcessorID());
182 }
183 TransferToCore(winner->GetPriority(), core_id, winner);
184 }
185 } else {
186 winner = next_thread;
187 }
188
189 return AskForReselectionOrMarkRedundant(yielding_thread, winner);
190}
191
192/*
193 * YieldThreadAndWaitForLoadBalancing takes a thread and moves it out of the scheduling queue
194 * and into the suggested queue. If no thread can be squeduled afterwards in that core,
195 * a suggested thread is obtained instead.
196 * This operation can be redundant and no scheduling is changed if marked as so.
197 */
198bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread) {
199 // Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section,
200 // etc.
201 Thread* winner = nullptr;
202 const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
203
204 // Remove the thread from its scheduled mlq, put it on the corresponding "suggested" one instead
205 TransferToCore(yielding_thread->GetPriority(), -1, yielding_thread);
206
207 // If the core is idle, perform load balancing, excluding the threads that have just used this
208 // function...
209 if (scheduled_queue[core_id].empty()) {
210 // Here, "current_threads" is calculated after the ""yield"", unlike yield -1
211 std::array<Thread*, NUM_CPU_CORES> current_threads;
212 for (u32 i = 0; i < NUM_CPU_CORES; i++) {
213 current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front();
214 }
215 for (auto& thread : suggested_queue[core_id]) {
216 const s32 source_core = thread->GetProcessorID();
217 if (source_core < 0 || thread == current_threads[source_core]) {
218 continue;
219 }
220 if (current_threads[source_core] == nullptr ||
221 current_threads[source_core]->GetPriority() >= min_regular_priority) {
222 winner = thread;
223 }
224 break;
225 }
226 if (winner != nullptr) {
227 if (winner != yielding_thread) {
228 if (winner->IsRunning()) {
229 UnloadThread(winner->GetProcessorID());
230 }
231 TransferToCore(winner->GetPriority(), core_id, winner);
232 }
233 } else {
234 winner = yielding_thread;
235 }
236 }
237
238 return AskForReselectionOrMarkRedundant(yielding_thread, winner);
239}
240
241void GlobalScheduler::PreemptThreads() {
242 for (std::size_t core_id = 0; core_id < NUM_CPU_CORES; core_id++) {
243 const u32 priority = preemption_priorities[core_id];
244
245 if (scheduled_queue[core_id].size(priority) > 0) {
246 scheduled_queue[core_id].front(priority)->IncrementYieldCount();
247 scheduled_queue[core_id].yield(priority);
248 if (scheduled_queue[core_id].size(priority) > 1) {
249 scheduled_queue[core_id].front(priority)->IncrementYieldCount();
250 }
251 }
252
253 Thread* current_thread =
254 scheduled_queue[core_id].empty() ? nullptr : scheduled_queue[core_id].front();
255 Thread* winner = nullptr;
256 for (auto& thread : suggested_queue[core_id]) {
257 const s32 source_core = thread->GetProcessorID();
258 if (thread->GetPriority() != priority) {
259 continue;
260 }
261 if (source_core >= 0) {
262 Thread* next_thread = scheduled_queue[source_core].empty()
263 ? nullptr
264 : scheduled_queue[source_core].front();
265 if (next_thread != nullptr && next_thread->GetPriority() < 2) {
266 break;
267 }
268 if (next_thread == thread) {
269 continue;
270 }
271 }
272 if (current_thread != nullptr &&
273 current_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks()) {
274 winner = thread;
275 break;
276 }
277 }
278
279 if (winner != nullptr) {
280 if (winner->IsRunning()) {
281 UnloadThread(winner->GetProcessorID());
282 }
283 TransferToCore(winner->GetPriority(), core_id, winner);
284 current_thread =
285 winner->GetPriority() <= current_thread->GetPriority() ? winner : current_thread;
286 }
287
288 if (current_thread != nullptr && current_thread->GetPriority() > priority) {
289 for (auto& thread : suggested_queue[core_id]) {
290 const s32 source_core = thread->GetProcessorID();
291 if (thread->GetPriority() < priority) {
292 continue;
293 }
294 if (source_core >= 0) {
295 Thread* next_thread = scheduled_queue[source_core].empty()
296 ? nullptr
297 : scheduled_queue[source_core].front();
298 if (next_thread != nullptr && next_thread->GetPriority() < 2) {
299 break;
300 }
301 if (next_thread == thread) {
302 continue;
303 }
304 }
305 if (current_thread != nullptr &&
306 current_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks()) {
307 winner = thread;
308 break;
309 }
310 }
311
312 if (winner != nullptr) {
313 if (winner->IsRunning()) {
314 UnloadThread(winner->GetProcessorID());
315 }
316 TransferToCore(winner->GetPriority(), core_id, winner);
317 current_thread = winner;
318 }
319 }
320
321 is_reselection_pending.store(true, std::memory_order_release);
322 }
323}
324
325void GlobalScheduler::Suggest(u32 priority, u32 core, Thread* thread) {
326 suggested_queue[core].add(thread, priority);
327}
328
329void GlobalScheduler::Unsuggest(u32 priority, u32 core, Thread* thread) {
330 suggested_queue[core].remove(thread, priority);
331}
332
333void GlobalScheduler::Schedule(u32 priority, u32 core, Thread* thread) {
334 ASSERT_MSG(thread->GetProcessorID() == core, "Thread must be assigned to this core.");
335 scheduled_queue[core].add(thread, priority);
336}
337
338void GlobalScheduler::SchedulePrepend(u32 priority, u32 core, Thread* thread) {
339 ASSERT_MSG(thread->GetProcessorID() == core, "Thread must be assigned to this core.");
340 scheduled_queue[core].add(thread, priority, false);
341}
342
343void GlobalScheduler::Reschedule(u32 priority, u32 core, Thread* thread) {
344 scheduled_queue[core].remove(thread, priority);
345 scheduled_queue[core].add(thread, priority);
346}
347
348void GlobalScheduler::Unschedule(u32 priority, u32 core, Thread* thread) {
349 scheduled_queue[core].remove(thread, priority);
350}
351
352void GlobalScheduler::TransferToCore(u32 priority, s32 destination_core, Thread* thread) {
353 const bool schedulable = thread->GetPriority() < THREADPRIO_COUNT;
354 const s32 source_core = thread->GetProcessorID();
355 if (source_core == destination_core || !schedulable) {
356 return;
357 }
358 thread->SetProcessorID(destination_core);
359 if (source_core >= 0) {
360 Unschedule(priority, source_core, thread);
361 }
362 if (destination_core >= 0) {
363 Unsuggest(priority, destination_core, thread);
364 Schedule(priority, destination_core, thread);
365 }
366 if (source_core >= 0) {
367 Suggest(priority, source_core, thread);
368 }
369}
21 370
22Scheduler::Scheduler(Core::System& system, Core::ARM_Interface& cpu_core) 371bool GlobalScheduler::AskForReselectionOrMarkRedundant(Thread* current_thread, Thread* winner) {
23 : cpu_core{cpu_core}, system{system} {} 372 if (current_thread == winner) {
373 current_thread->IncrementYieldCount();
374 return true;
375 } else {
376 is_reselection_pending.store(true, std::memory_order_release);
377 return false;
378 }
379}
24 380
25Scheduler::~Scheduler() { 381void GlobalScheduler::Shutdown() {
26 for (auto& thread : thread_list) { 382 for (std::size_t core = 0; core < NUM_CPU_CORES; core++) {
27 thread->Stop(); 383 scheduled_queue[core].clear();
384 suggested_queue[core].clear();
28 } 385 }
386 thread_list.clear();
29} 387}
30 388
389GlobalScheduler::~GlobalScheduler() = default;
390
391Scheduler::Scheduler(Core::System& system, Core::ARM_Interface& cpu_core, u32 core_id)
392 : system(system), cpu_core(cpu_core), core_id(core_id) {}
393
394Scheduler::~Scheduler() = default;
395
31bool Scheduler::HaveReadyThreads() const { 396bool Scheduler::HaveReadyThreads() const {
32 std::lock_guard lock{scheduler_mutex}; 397 return system.GlobalScheduler().HaveReadyThreads(core_id);
33 return !ready_queue.empty();
34} 398}
35 399
36Thread* Scheduler::GetCurrentThread() const { 400Thread* Scheduler::GetCurrentThread() const {
37 return current_thread.get(); 401 return current_thread.get();
38} 402}
39 403
404Thread* Scheduler::GetSelectedThread() const {
405 return selected_thread.get();
406}
407
408void Scheduler::SelectThreads() {
409 system.GlobalScheduler().SelectThread(core_id);
410}
411
40u64 Scheduler::GetLastContextSwitchTicks() const { 412u64 Scheduler::GetLastContextSwitchTicks() const {
41 return last_context_switch_time; 413 return last_context_switch_time;
42} 414}
43 415
44Thread* Scheduler::PopNextReadyThread() { 416void Scheduler::TryDoContextSwitch() {
45 Thread* next = nullptr; 417 if (is_context_switch_pending) {
46 Thread* thread = GetCurrentThread(); 418 SwitchContext();
419 }
420}
47 421
48 if (thread && thread->GetStatus() == ThreadStatus::Running) { 422void Scheduler::UnloadThread() {
49 if (ready_queue.empty()) { 423 Thread* const previous_thread = GetCurrentThread();
50 return thread; 424 Process* const previous_process = system.Kernel().CurrentProcess();
51 } 425
52 // We have to do better than the current thread. 426 UpdateLastContextSwitchTime(previous_thread, previous_process);
53 // This call returns null when that's not possible. 427
54 next = ready_queue.front(); 428 // Save context for previous thread
55 if (next == nullptr || next->GetPriority() >= thread->GetPriority()) { 429 if (previous_thread) {
56 next = thread; 430 cpu_core.SaveContext(previous_thread->GetContext());
57 } 431 // Save the TPIDR_EL0 system register in case it was modified.
58 } else { 432 previous_thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
59 if (ready_queue.empty()) { 433
60 return nullptr; 434 if (previous_thread->GetStatus() == ThreadStatus::Running) {
435 // This is only the case when a reschedule is triggered without the current thread
436 // yielding execution (i.e. an event triggered, system core time-sliced, etc)
437 previous_thread->SetStatus(ThreadStatus::Ready);
61 } 438 }
62 next = ready_queue.front(); 439 previous_thread->SetIsRunning(false);
63 } 440 }
64 441 current_thread = nullptr;
65 return next;
66} 442}
67 443
68void Scheduler::SwitchContext(Thread* new_thread) { 444void Scheduler::SwitchContext() {
69 Thread* previous_thread = GetCurrentThread(); 445 Thread* const previous_thread = GetCurrentThread();
446 Thread* const new_thread = GetSelectedThread();
447
448 is_context_switch_pending = false;
449 if (new_thread == previous_thread) {
450 return;
451 }
452
70 Process* const previous_process = system.Kernel().CurrentProcess(); 453 Process* const previous_process = system.Kernel().CurrentProcess();
71 454
72 UpdateLastContextSwitchTime(previous_thread, previous_process); 455 UpdateLastContextSwitchTime(previous_thread, previous_process);
@@ -80,23 +463,23 @@ void Scheduler::SwitchContext(Thread* new_thread) {
80 if (previous_thread->GetStatus() == ThreadStatus::Running) { 463 if (previous_thread->GetStatus() == ThreadStatus::Running) {
81 // This is only the case when a reschedule is triggered without the current thread 464 // This is only the case when a reschedule is triggered without the current thread
82 // yielding execution (i.e. an event triggered, system core time-sliced, etc) 465 // yielding execution (i.e. an event triggered, system core time-sliced, etc)
83 ready_queue.add(previous_thread, previous_thread->GetPriority(), false);
84 previous_thread->SetStatus(ThreadStatus::Ready); 466 previous_thread->SetStatus(ThreadStatus::Ready);
85 } 467 }
468 previous_thread->SetIsRunning(false);
86 } 469 }
87 470
88 // Load context of new thread 471 // Load context of new thread
89 if (new_thread) { 472 if (new_thread) {
473 ASSERT_MSG(new_thread->GetProcessorID() == this->core_id,
474 "Thread must be assigned to this core.");
90 ASSERT_MSG(new_thread->GetStatus() == ThreadStatus::Ready, 475 ASSERT_MSG(new_thread->GetStatus() == ThreadStatus::Ready,
91 "Thread must be ready to become running."); 476 "Thread must be ready to become running.");
92 477
93 // Cancel any outstanding wakeup events for this thread 478 // Cancel any outstanding wakeup events for this thread
94 new_thread->CancelWakeupTimer(); 479 new_thread->CancelWakeupTimer();
95
96 current_thread = new_thread; 480 current_thread = new_thread;
97
98 ready_queue.remove(new_thread, new_thread->GetPriority());
99 new_thread->SetStatus(ThreadStatus::Running); 481 new_thread->SetStatus(ThreadStatus::Running);
482 new_thread->SetIsRunning(true);
100 483
101 auto* const thread_owner_process = current_thread->GetOwnerProcess(); 484 auto* const thread_owner_process = current_thread->GetOwnerProcess();
102 if (previous_process != thread_owner_process) { 485 if (previous_process != thread_owner_process) {
@@ -130,124 +513,9 @@ void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
130 last_context_switch_time = most_recent_switch_ticks; 513 last_context_switch_time = most_recent_switch_ticks;
131} 514}
132 515
133void Scheduler::Reschedule() { 516void Scheduler::Shutdown() {
134 std::lock_guard lock{scheduler_mutex}; 517 current_thread = nullptr;
135 518 selected_thread = nullptr;
136 Thread* cur = GetCurrentThread();
137 Thread* next = PopNextReadyThread();
138
139 if (cur && next) {
140 LOG_TRACE(Kernel, "context switch {} -> {}", cur->GetObjectId(), next->GetObjectId());
141 } else if (cur) {
142 LOG_TRACE(Kernel, "context switch {} -> idle", cur->GetObjectId());
143 } else if (next) {
144 LOG_TRACE(Kernel, "context switch idle -> {}", next->GetObjectId());
145 }
146
147 SwitchContext(next);
148}
149
150void Scheduler::AddThread(SharedPtr<Thread> thread) {
151 std::lock_guard lock{scheduler_mutex};
152
153 thread_list.push_back(std::move(thread));
154}
155
156void Scheduler::RemoveThread(Thread* thread) {
157 std::lock_guard lock{scheduler_mutex};
158
159 thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
160 thread_list.end());
161}
162
163void Scheduler::ScheduleThread(Thread* thread, u32 priority) {
164 std::lock_guard lock{scheduler_mutex};
165
166 ASSERT(thread->GetStatus() == ThreadStatus::Ready);
167 ready_queue.add(thread, priority);
168}
169
170void Scheduler::UnscheduleThread(Thread* thread, u32 priority) {
171 std::lock_guard lock{scheduler_mutex};
172
173 ASSERT(thread->GetStatus() == ThreadStatus::Ready);
174 ready_queue.remove(thread, priority);
175}
176
177void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
178 std::lock_guard lock{scheduler_mutex};
179 if (thread->GetPriority() == priority) {
180 return;
181 }
182
183 // If thread was ready, adjust queues
184 if (thread->GetStatus() == ThreadStatus::Ready)
185 ready_queue.adjust(thread, thread->GetPriority(), priority);
186}
187
188Thread* Scheduler::GetNextSuggestedThread(u32 core, u32 maximum_priority) const {
189 std::lock_guard lock{scheduler_mutex};
190
191 const u32 mask = 1U << core;
192 for (auto* thread : ready_queue) {
193 if ((thread->GetAffinityMask() & mask) != 0 && thread->GetPriority() < maximum_priority) {
194 return thread;
195 }
196 }
197 return nullptr;
198}
199
200void Scheduler::YieldWithoutLoadBalancing(Thread* thread) {
201 ASSERT(thread != nullptr);
202 // Avoid yielding if the thread isn't even running.
203 ASSERT(thread->GetStatus() == ThreadStatus::Running);
204
205 // Sanity check that the priority is valid
206 ASSERT(thread->GetPriority() < THREADPRIO_COUNT);
207
208 // Yield this thread -- sleep for zero time and force reschedule to different thread
209 GetCurrentThread()->Sleep(0);
210}
211
212void Scheduler::YieldWithLoadBalancing(Thread* thread) {
213 ASSERT(thread != nullptr);
214 const auto priority = thread->GetPriority();
215 const auto core = static_cast<u32>(thread->GetProcessorID());
216
217 // Avoid yielding if the thread isn't even running.
218 ASSERT(thread->GetStatus() == ThreadStatus::Running);
219
220 // Sanity check that the priority is valid
221 ASSERT(priority < THREADPRIO_COUNT);
222
223 // Sleep for zero time to be able to force reschedule to different thread
224 GetCurrentThread()->Sleep(0);
225
226 Thread* suggested_thread = nullptr;
227
228 // Search through all of the cpu cores (except this one) for a suggested thread.
229 // Take the first non-nullptr one
230 for (unsigned cur_core = 0; cur_core < Core::NUM_CPU_CORES; ++cur_core) {
231 const auto res =
232 system.CpuCore(cur_core).Scheduler().GetNextSuggestedThread(core, priority);
233
234 // If scheduler provides a suggested thread
235 if (res != nullptr) {
236 // And its better than the current suggested thread (or is the first valid one)
237 if (suggested_thread == nullptr ||
238 suggested_thread->GetPriority() > res->GetPriority()) {
239 suggested_thread = res;
240 }
241 }
242 }
243
244 // If a suggested thread was found, queue that for this core
245 if (suggested_thread != nullptr)
246 suggested_thread->ChangeCore(core, suggested_thread->GetAffinityMask());
247}
248
249void Scheduler::YieldAndWaitForLoadBalancing(Thread* thread) {
250 UNIMPLEMENTED_MSG("Wait for load balancing thread yield type is not implemented!");
251} 519}
252 520
253} // namespace Kernel 521} // namespace Kernel
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
index b29bf7be8..fcae28e0a 100644
--- a/src/core/hle/kernel/scheduler.h
+++ b/src/core/hle/kernel/scheduler.h
@@ -20,124 +20,172 @@ namespace Kernel {
20 20
21class Process; 21class Process;
22 22
23class Scheduler final { 23class GlobalScheduler final {
24public: 24public:
25 explicit Scheduler(Core::System& system, Core::ARM_Interface& cpu_core); 25 static constexpr u32 NUM_CPU_CORES = 4;
26 ~Scheduler();
27 26
28 /// Returns whether there are any threads that are ready to run. 27 explicit GlobalScheduler(Core::System& system);
29 bool HaveReadyThreads() const; 28 ~GlobalScheduler();
29 /// Adds a new thread to the scheduler
30 void AddThread(SharedPtr<Thread> thread);
30 31
31 /// Reschedules to the next available thread (call after current thread is suspended) 32 /// Removes a thread from the scheduler
32 void Reschedule(); 33 void RemoveThread(const Thread* thread);
33 34
34 /// Gets the current running thread 35 /// Returns a list of all threads managed by the scheduler
35 Thread* GetCurrentThread() const; 36 const std::vector<SharedPtr<Thread>>& GetThreadList() const {
37 return thread_list;
38 }
36 39
37 /// Gets the timestamp for the last context switch in ticks. 40 // Add a thread to the suggested queue of a cpu core. Suggested threads may be
38 u64 GetLastContextSwitchTicks() const; 41 // picked if no thread is scheduled to run on the core.
42 void Suggest(u32 priority, u32 core, Thread* thread);
39 43
40 /// Adds a new thread to the scheduler 44 // Remove a thread to the suggested queue of a cpu core. Suggested threads may be
41 void AddThread(SharedPtr<Thread> thread); 45 // picked if no thread is scheduled to run on the core.
46 void Unsuggest(u32 priority, u32 core, Thread* thread);
42 47
43 /// Removes a thread from the scheduler 48 // Add a thread to the scheduling queue of a cpu core. The thread is added at the
44 void RemoveThread(Thread* thread); 49 // back the queue in its priority level
50 void Schedule(u32 priority, u32 core, Thread* thread);
45 51
46 /// Schedules a thread that has become "ready" 52 // Add a thread to the scheduling queue of a cpu core. The thread is added at the
47 void ScheduleThread(Thread* thread, u32 priority); 53 // front the queue in its priority level
54 void SchedulePrepend(u32 priority, u32 core, Thread* thread);
48 55
49 /// Unschedules a thread that was already scheduled 56 // Reschedule an already scheduled thread based on a new priority
50 void UnscheduleThread(Thread* thread, u32 priority); 57 void Reschedule(u32 priority, u32 core, Thread* thread);
51 58
52 /// Sets the priority of a thread in the scheduler 59 // Unschedule a thread.
53 void SetThreadPriority(Thread* thread, u32 priority); 60 void Unschedule(u32 priority, u32 core, Thread* thread);
54 61
55 /// Gets the next suggested thread for load balancing 62 // Transfers a thread into an specific core. If the destination_core is -1
56 Thread* GetNextSuggestedThread(u32 core, u32 minimum_priority) const; 63 // it will be unscheduled from its source code and added into its suggested
64 // queue.
65 void TransferToCore(u32 priority, s32 destination_core, Thread* thread);
57 66
58 /** 67 /*
59 * YieldWithoutLoadBalancing -- analogous to normal yield on a system 68 * UnloadThread selects a core and forces it to unload its current thread's context
60 * Moves the thread to the end of the ready queue for its priority, and then reschedules the
61 * system to the new head of the queue.
62 *
63 * Example (Single Core -- but can be extrapolated to multi):
64 * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC (->exec order->)
65 * Currently Running: ThreadR
66 *
67 * ThreadR calls YieldWithoutLoadBalancing
68 *
69 * ThreadR is moved to the end of ready_queue[prio=0]:
70 * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC, ThreadR (->exec order->)
71 * Currently Running: Nothing
72 *
73 * System is rescheduled (ThreadA is popped off of queue):
74 * ready_queue[prio=0]: ThreadB, ThreadC, ThreadR (->exec order->)
75 * Currently Running: ThreadA
76 *
77 * If the queue is empty at time of call, no yielding occurs. This does not cross between cores
78 * or priorities at all.
79 */ 69 */
80 void YieldWithoutLoadBalancing(Thread* thread); 70 void UnloadThread(s32 core);
71
72 /*
73 * SelectThread takes care of selecting the new scheduled thread.
74 * It does it in 3 steps:
75 * - First a thread is selected from the top of the priority queue. If no thread
76 * is obtained then we move to step two, else we are done.
77 * - Second we try to get a suggested thread that's not assigned to any core or
78 * that is not the top thread in that core.
79 * - Third is no suggested thread is found, we do a second pass and pick a running
80 * thread in another core and swap it with its current thread.
81 */
82 void SelectThread(u32 core);
81 83
82 /** 84 bool HaveReadyThreads(u32 core_id) const {
83 * YieldWithLoadBalancing -- yield but with better selection of the new running thread 85 return !scheduled_queue[core_id].empty();
84 * Moves the current thread to the end of the ready queue for its priority, then selects a 86 }
85 * 'suggested thread' (a thread on a different core that could run on this core) from the 87
86 * scheduler, changes its core, and reschedules the current core to that thread. 88 /*
87 * 89 * YieldThread takes a thread and moves it to the back of the it's priority list
88 * Example (Dual Core -- can be extrapolated to Quad Core, this is just normal yield if it were 90 * This operation can be redundant and no scheduling is changed if marked as so.
89 * single core):
90 * ready_queue[core=0][prio=0]: ThreadA, ThreadB (affinities not pictured as irrelevant
91 * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only]
92 * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1
93 *
94 * ThreadQ calls YieldWithLoadBalancing
95 *
96 * ThreadQ is moved to the end of ready_queue[core=0][prio=0]:
97 * ready_queue[core=0][prio=0]: ThreadA, ThreadB
98 * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only]
99 * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1
100 *
101 * A list of suggested threads for each core is compiled
102 * Suggested Threads: {ThreadC on Core 1}
103 * If this were quad core (as the switch is), there could be between 0 and 3 threads in this
104 * list. If there are more than one, the thread is selected by highest prio.
105 *
106 * ThreadC is core changed to Core 0:
107 * ready_queue[core=0][prio=0]: ThreadC, ThreadA, ThreadB, ThreadQ
108 * ready_queue[core=1][prio=0]: ThreadD
109 * Currently Running: None on Core 0 || ThreadP on Core 1
110 *
111 * System is rescheduled (ThreadC is popped off of queue):
112 * ready_queue[core=0][prio=0]: ThreadA, ThreadB, ThreadQ
113 * ready_queue[core=1][prio=0]: ThreadD
114 * Currently Running: ThreadC on Core 0 || ThreadP on Core 1
115 *
116 * If no suggested threads can be found this will behave just as normal yield. If there are
117 * multiple candidates for the suggested thread on a core, the highest prio is taken.
118 */ 91 */
119 void YieldWithLoadBalancing(Thread* thread); 92 bool YieldThread(Thread* thread);
120 93
121 /// Currently unknown -- asserts as unimplemented on call 94 /*
122 void YieldAndWaitForLoadBalancing(Thread* thread); 95 * YieldThreadAndBalanceLoad takes a thread and moves it to the back of the it's priority list.
96 * Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or
97 * a better priority than the next thread in the core.
98 * This operation can be redundant and no scheduling is changed if marked as so.
99 */
100 bool YieldThreadAndBalanceLoad(Thread* thread);
123 101
124 /// Returns a list of all threads managed by the scheduler 102 /*
125 const std::vector<SharedPtr<Thread>>& GetThreadList() const { 103 * YieldThreadAndWaitForLoadBalancing takes a thread and moves it out of the scheduling queue
126 return thread_list; 104 * and into the suggested queue. If no thread can be squeduled afterwards in that core,
105 * a suggested thread is obtained instead.
106 * This operation can be redundant and no scheduling is changed if marked as so.
107 */
108 bool YieldThreadAndWaitForLoadBalancing(Thread* thread);
109
110 /*
111 * PreemptThreads this operation rotates the scheduling queues of threads at
112 * a preemption priority and then does some core rebalancing. Preemption priorities
113 * can be found in the array 'preemption_priorities'. This operation happens
114 * every 10ms.
115 */
116 void PreemptThreads();
117
118 u32 CpuCoresCount() const {
119 return NUM_CPU_CORES;
120 }
121
122 void SetReselectionPending() {
123 is_reselection_pending.store(true, std::memory_order_release);
127 } 124 }
128 125
126 bool IsReselectionPending() const {
127 return is_reselection_pending.load(std::memory_order_acquire);
128 }
129
130 void Shutdown();
131
129private: 132private:
130 /** 133 bool AskForReselectionOrMarkRedundant(Thread* current_thread, Thread* winner);
131 * Pops and returns the next thread from the thread queue 134
132 * @return A pointer to the next ready thread 135 static constexpr u32 min_regular_priority = 2;
133 */ 136 std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, NUM_CPU_CORES> scheduled_queue;
134 Thread* PopNextReadyThread(); 137 std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, NUM_CPU_CORES> suggested_queue;
138 std::atomic<bool> is_reselection_pending;
139
140 // `preemption_priorities` are the priority levels at which the global scheduler
141 // preempts threads every 10 ms. They are ordered from Core 0 to Core 3
142 std::array<u32, NUM_CPU_CORES> preemption_priorities = {59, 59, 59, 62};
143
144 /// Lists all thread ids that aren't deleted/etc.
145 std::vector<SharedPtr<Thread>> thread_list;
146 Core::System& system;
147};
148
149class Scheduler final {
150public:
151 explicit Scheduler(Core::System& system, Core::ARM_Interface& cpu_core, u32 core_id);
152 ~Scheduler();
153
154 /// Returns whether there are any threads that are ready to run.
155 bool HaveReadyThreads() const;
135 156
157 /// Reschedules to the next available thread (call after current thread is suspended)
158 void TryDoContextSwitch();
159
160 /// Unloads currently running thread
161 void UnloadThread();
162
163 /// Select the threads in top of the scheduling multilist.
164 void SelectThreads();
165
166 /// Gets the current running thread
167 Thread* GetCurrentThread() const;
168
169 /// Gets the currently selected thread from the top of the multilevel queue
170 Thread* GetSelectedThread() const;
171
172 /// Gets the timestamp for the last context switch in ticks.
173 u64 GetLastContextSwitchTicks() const;
174
175 bool ContextSwitchPending() const {
176 return is_context_switch_pending;
177 }
178
179 /// Shutdowns the scheduler.
180 void Shutdown();
181
182private:
183 friend class GlobalScheduler;
136 /** 184 /**
137 * Switches the CPU's active thread context to that of the specified thread 185 * Switches the CPU's active thread context to that of the specified thread
138 * @param new_thread The thread to switch to 186 * @param new_thread The thread to switch to
139 */ 187 */
140 void SwitchContext(Thread* new_thread); 188 void SwitchContext();
141 189
142 /** 190 /**
143 * Called on every context switch to update the internal timestamp 191 * Called on every context switch to update the internal timestamp
@@ -152,19 +200,16 @@ private:
152 */ 200 */
153 void UpdateLastContextSwitchTime(Thread* thread, Process* process); 201 void UpdateLastContextSwitchTime(Thread* thread, Process* process);
154 202
155 /// Lists all thread ids that aren't deleted/etc.
156 std::vector<SharedPtr<Thread>> thread_list;
157
158 /// Lists only ready thread ids.
159 Common::MultiLevelQueue<Thread*, THREADPRIO_LOWEST + 1> ready_queue;
160
161 SharedPtr<Thread> current_thread = nullptr; 203 SharedPtr<Thread> current_thread = nullptr;
204 SharedPtr<Thread> selected_thread = nullptr;
162 205
206 Core::System& system;
163 Core::ARM_Interface& cpu_core; 207 Core::ARM_Interface& cpu_core;
164 u64 last_context_switch_time = 0; 208 u64 last_context_switch_time = 0;
209 u64 idle_selection_count = 0;
210 const u32 core_id;
165 211
166 Core::System& system; 212 bool is_context_switch_pending = false;
167 static std::mutex scheduler_mutex;
168}; 213};
169 214
170} // namespace Kernel 215} // namespace Kernel
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 1fd1a732a..f64236be1 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -516,7 +516,7 @@ static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr
516 thread->WakeAfterDelay(nano_seconds); 516 thread->WakeAfterDelay(nano_seconds);
517 thread->SetWakeupCallback(DefaultThreadWakeupCallback); 517 thread->SetWakeupCallback(DefaultThreadWakeupCallback);
518 518
519 system.CpuCore(thread->GetProcessorID()).PrepareReschedule(); 519 system.PrepareReschedule(thread->GetProcessorID());
520 520
521 return RESULT_TIMEOUT; 521 return RESULT_TIMEOUT;
522} 522}
@@ -534,6 +534,7 @@ static ResultCode CancelSynchronization(Core::System& system, Handle thread_hand
534 } 534 }
535 535
536 thread->CancelWait(); 536 thread->CancelWait();
537 system.PrepareReschedule(thread->GetProcessorID());
537 return RESULT_SUCCESS; 538 return RESULT_SUCCESS;
538} 539}
539 540
@@ -1066,6 +1067,8 @@ static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 act
1066 } 1067 }
1067 1068
1068 thread->SetActivity(static_cast<ThreadActivity>(activity)); 1069 thread->SetActivity(static_cast<ThreadActivity>(activity));
1070
1071 system.PrepareReschedule(thread->GetProcessorID());
1069 return RESULT_SUCCESS; 1072 return RESULT_SUCCESS;
1070} 1073}
1071 1074
@@ -1147,7 +1150,7 @@ static ResultCode SetThreadPriority(Core::System& system, Handle handle, u32 pri
1147 1150
1148 thread->SetPriority(priority); 1151 thread->SetPriority(priority);
1149 1152
1150 system.CpuCore(thread->GetProcessorID()).PrepareReschedule(); 1153 system.PrepareReschedule(thread->GetProcessorID());
1151 return RESULT_SUCCESS; 1154 return RESULT_SUCCESS;
1152} 1155}
1153 1156
@@ -1503,7 +1506,7 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e
1503 thread->SetName( 1506 thread->SetName(
1504 fmt::format("thread[entry_point={:X}, handle={:X}]", entry_point, *new_thread_handle)); 1507 fmt::format("thread[entry_point={:X}, handle={:X}]", entry_point, *new_thread_handle));
1505 1508
1506 system.CpuCore(thread->GetProcessorID()).PrepareReschedule(); 1509 system.PrepareReschedule(thread->GetProcessorID());
1507 1510
1508 return RESULT_SUCCESS; 1511 return RESULT_SUCCESS;
1509} 1512}
@@ -1525,7 +1528,7 @@ static ResultCode StartThread(Core::System& system, Handle thread_handle) {
1525 thread->ResumeFromWait(); 1528 thread->ResumeFromWait();
1526 1529
1527 if (thread->GetStatus() == ThreadStatus::Ready) { 1530 if (thread->GetStatus() == ThreadStatus::Ready) {
1528 system.CpuCore(thread->GetProcessorID()).PrepareReschedule(); 1531 system.PrepareReschedule(thread->GetProcessorID());
1529 } 1532 }
1530 1533
1531 return RESULT_SUCCESS; 1534 return RESULT_SUCCESS;
@@ -1537,7 +1540,7 @@ static void ExitThread(Core::System& system) {
1537 1540
1538 auto* const current_thread = system.CurrentScheduler().GetCurrentThread(); 1541 auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
1539 current_thread->Stop(); 1542 current_thread->Stop();
1540 system.CurrentScheduler().RemoveThread(current_thread); 1543 system.GlobalScheduler().RemoveThread(current_thread);
1541 system.PrepareReschedule(); 1544 system.PrepareReschedule();
1542} 1545}
1543 1546
@@ -1553,17 +1556,18 @@ static void SleepThread(Core::System& system, s64 nanoseconds) {
1553 1556
1554 auto& scheduler = system.CurrentScheduler(); 1557 auto& scheduler = system.CurrentScheduler();
1555 auto* const current_thread = scheduler.GetCurrentThread(); 1558 auto* const current_thread = scheduler.GetCurrentThread();
1559 bool is_redundant = false;
1556 1560
1557 if (nanoseconds <= 0) { 1561 if (nanoseconds <= 0) {
1558 switch (static_cast<SleepType>(nanoseconds)) { 1562 switch (static_cast<SleepType>(nanoseconds)) {
1559 case SleepType::YieldWithoutLoadBalancing: 1563 case SleepType::YieldWithoutLoadBalancing:
1560 scheduler.YieldWithoutLoadBalancing(current_thread); 1564 is_redundant = current_thread->YieldSimple();
1561 break; 1565 break;
1562 case SleepType::YieldWithLoadBalancing: 1566 case SleepType::YieldWithLoadBalancing:
1563 scheduler.YieldWithLoadBalancing(current_thread); 1567 is_redundant = current_thread->YieldAndBalanceLoad();
1564 break; 1568 break;
1565 case SleepType::YieldAndWaitForLoadBalancing: 1569 case SleepType::YieldAndWaitForLoadBalancing:
1566 scheduler.YieldAndWaitForLoadBalancing(current_thread); 1570 is_redundant = current_thread->YieldAndWaitForLoadBalancing();
1567 break; 1571 break;
1568 default: 1572 default:
1569 UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds); 1573 UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
@@ -1572,10 +1576,13 @@ static void SleepThread(Core::System& system, s64 nanoseconds) {
1572 current_thread->Sleep(nanoseconds); 1576 current_thread->Sleep(nanoseconds);
1573 } 1577 }
1574 1578
1575 // Reschedule all CPU cores 1579 if (is_redundant) {
1576 for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i) { 1580 // If it's redundant, the core is pretty much idle. Some games keep idling
1577 system.CpuCore(i).PrepareReschedule(); 1581 // a core while it's doing nothing, we advance timing to avoid costly continuous
1582 // calls.
1583 system.CoreTiming().AddTicks(2000);
1578 } 1584 }
1585 system.PrepareReschedule(current_thread->GetProcessorID());
1579} 1586}
1580 1587
1581/// Wait process wide key atomic 1588/// Wait process wide key atomic
@@ -1601,6 +1608,8 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add
1601 return ERR_INVALID_ADDRESS; 1608 return ERR_INVALID_ADDRESS;
1602 } 1609 }
1603 1610
1611 ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4));
1612
1604 auto* const current_process = system.Kernel().CurrentProcess(); 1613 auto* const current_process = system.Kernel().CurrentProcess();
1605 const auto& handle_table = current_process->GetHandleTable(); 1614 const auto& handle_table = current_process->GetHandleTable();
1606 SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); 1615 SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
@@ -1622,7 +1631,7 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add
1622 1631
1623 // Note: Deliberately don't attempt to inherit the lock owner's priority. 1632 // Note: Deliberately don't attempt to inherit the lock owner's priority.
1624 1633
1625 system.CpuCore(current_thread->GetProcessorID()).PrepareReschedule(); 1634 system.PrepareReschedule(current_thread->GetProcessorID());
1626 return RESULT_SUCCESS; 1635 return RESULT_SUCCESS;
1627} 1636}
1628 1637
@@ -1632,24 +1641,19 @@ static ResultCode SignalProcessWideKey(Core::System& system, VAddr condition_var
1632 LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}", 1641 LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}",
1633 condition_variable_addr, target); 1642 condition_variable_addr, target);
1634 1643
1635 const auto RetrieveWaitingThreads = [&system](std::size_t core_index, 1644 ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4));
1636 std::vector<SharedPtr<Thread>>& waiting_threads,
1637 VAddr condvar_addr) {
1638 const auto& scheduler = system.Scheduler(core_index);
1639 const auto& thread_list = scheduler.GetThreadList();
1640
1641 for (const auto& thread : thread_list) {
1642 if (thread->GetCondVarWaitAddress() == condvar_addr)
1643 waiting_threads.push_back(thread);
1644 }
1645 };
1646 1645
1647 // Retrieve a list of all threads that are waiting for this condition variable. 1646 // Retrieve a list of all threads that are waiting for this condition variable.
1648 std::vector<SharedPtr<Thread>> waiting_threads; 1647 std::vector<SharedPtr<Thread>> waiting_threads;
1649 RetrieveWaitingThreads(0, waiting_threads, condition_variable_addr); 1648 const auto& scheduler = system.GlobalScheduler();
1650 RetrieveWaitingThreads(1, waiting_threads, condition_variable_addr); 1649 const auto& thread_list = scheduler.GetThreadList();
1651 RetrieveWaitingThreads(2, waiting_threads, condition_variable_addr); 1650
1652 RetrieveWaitingThreads(3, waiting_threads, condition_variable_addr); 1651 for (const auto& thread : thread_list) {
1652 if (thread->GetCondVarWaitAddress() == condition_variable_addr) {
1653 waiting_threads.push_back(thread);
1654 }
1655 }
1656
1653 // Sort them by priority, such that the highest priority ones come first. 1657 // Sort them by priority, such that the highest priority ones come first.
1654 std::sort(waiting_threads.begin(), waiting_threads.end(), 1658 std::sort(waiting_threads.begin(), waiting_threads.end(),
1655 [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) { 1659 [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
@@ -1679,18 +1683,20 @@ static ResultCode SignalProcessWideKey(Core::System& system, VAddr condition_var
1679 1683
1680 // Atomically read the value of the mutex. 1684 // Atomically read the value of the mutex.
1681 u32 mutex_val = 0; 1685 u32 mutex_val = 0;
1686 u32 update_val = 0;
1687 const VAddr mutex_address = thread->GetMutexWaitAddress();
1682 do { 1688 do {
1683 monitor.SetExclusive(current_core, thread->GetMutexWaitAddress()); 1689 monitor.SetExclusive(current_core, mutex_address);
1684 1690
1685 // If the mutex is not yet acquired, acquire it. 1691 // If the mutex is not yet acquired, acquire it.
1686 mutex_val = Memory::Read32(thread->GetMutexWaitAddress()); 1692 mutex_val = Memory::Read32(mutex_address);
1687 1693
1688 if (mutex_val != 0) { 1694 if (mutex_val != 0) {
1689 monitor.ClearExclusive(); 1695 update_val = mutex_val | Mutex::MutexHasWaitersFlag;
1690 break; 1696 } else {
1697 update_val = thread->GetWaitHandle();
1691 } 1698 }
1692 } while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(), 1699 } while (!monitor.ExclusiveWrite32(current_core, mutex_address, update_val));
1693 thread->GetWaitHandle()));
1694 if (mutex_val == 0) { 1700 if (mutex_val == 0) {
1695 // We were able to acquire the mutex, resume this thread. 1701 // We were able to acquire the mutex, resume this thread.
1696 ASSERT(thread->GetStatus() == ThreadStatus::WaitCondVar); 1702 ASSERT(thread->GetStatus() == ThreadStatus::WaitCondVar);
@@ -1704,20 +1710,9 @@ static ResultCode SignalProcessWideKey(Core::System& system, VAddr condition_var
1704 thread->SetLockOwner(nullptr); 1710 thread->SetLockOwner(nullptr);
1705 thread->SetMutexWaitAddress(0); 1711 thread->SetMutexWaitAddress(0);
1706 thread->SetWaitHandle(0); 1712 thread->SetWaitHandle(0);
1707 system.CpuCore(thread->GetProcessorID()).PrepareReschedule(); 1713 thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
1714 system.PrepareReschedule(thread->GetProcessorID());
1708 } else { 1715 } else {
1709 // Atomically signal that the mutex now has a waiting thread.
1710 do {
1711 monitor.SetExclusive(current_core, thread->GetMutexWaitAddress());
1712
1713 // Ensure that the mutex value is still what we expect.
1714 u32 value = Memory::Read32(thread->GetMutexWaitAddress());
1715 // TODO(Subv): When this happens, the kernel just clears the exclusive state and
1716 // retries the initial read for this thread.
1717 ASSERT_MSG(mutex_val == value, "Unhandled synchronization primitive case");
1718 } while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
1719 mutex_val | Mutex::MutexHasWaitersFlag));
1720
1721 // The mutex is already owned by some other thread, make this thread wait on it. 1716 // The mutex is already owned by some other thread, make this thread wait on it.
1722 const Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask); 1717 const Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
1723 const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); 1718 const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
@@ -1728,6 +1723,7 @@ static ResultCode SignalProcessWideKey(Core::System& system, VAddr condition_var
1728 thread->SetStatus(ThreadStatus::WaitMutex); 1723 thread->SetStatus(ThreadStatus::WaitMutex);
1729 1724
1730 owner->AddMutexWaiter(thread); 1725 owner->AddMutexWaiter(thread);
1726 system.PrepareReschedule(thread->GetProcessorID());
1731 } 1727 }
1732 } 1728 }
1733 1729
@@ -1754,7 +1750,12 @@ static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type,
1754 1750
1755 const auto arbitration_type = static_cast<AddressArbiter::ArbitrationType>(type); 1751 const auto arbitration_type = static_cast<AddressArbiter::ArbitrationType>(type);
1756 auto& address_arbiter = system.Kernel().CurrentProcess()->GetAddressArbiter(); 1752 auto& address_arbiter = system.Kernel().CurrentProcess()->GetAddressArbiter();
1757 return address_arbiter.WaitForAddress(address, arbitration_type, value, timeout); 1753 const ResultCode result =
1754 address_arbiter.WaitForAddress(address, arbitration_type, value, timeout);
1755 if (result == RESULT_SUCCESS) {
1756 system.PrepareReschedule();
1757 }
1758 return result;
1758} 1759}
1759 1760
1760// Signals to an address (via Address Arbiter) 1761// Signals to an address (via Address Arbiter)
@@ -2040,7 +2041,10 @@ static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle,
2040 return ERR_INVALID_HANDLE; 2041 return ERR_INVALID_HANDLE;
2041 } 2042 }
2042 2043
2044 system.PrepareReschedule(thread->GetProcessorID());
2043 thread->ChangeCore(core, affinity_mask); 2045 thread->ChangeCore(core, affinity_mask);
2046 system.PrepareReschedule(thread->GetProcessorID());
2047
2044 return RESULT_SUCCESS; 2048 return RESULT_SUCCESS;
2045} 2049}
2046 2050
@@ -2151,6 +2155,7 @@ static ResultCode SignalEvent(Core::System& system, Handle handle) {
2151 } 2155 }
2152 2156
2153 writable_event->Signal(); 2157 writable_event->Signal();
2158 system.PrepareReschedule();
2154 return RESULT_SUCCESS; 2159 return RESULT_SUCCESS;
2155} 2160}
2156 2161
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index ec529e7f2..962530d2d 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -45,15 +45,7 @@ void Thread::Stop() {
45 callback_handle); 45 callback_handle);
46 kernel.ThreadWakeupCallbackHandleTable().Close(callback_handle); 46 kernel.ThreadWakeupCallbackHandleTable().Close(callback_handle);
47 callback_handle = 0; 47 callback_handle = 0;
48 48 SetStatus(ThreadStatus::Dead);
49 // Clean up thread from ready queue
50 // This is only needed when the thread is terminated forcefully (SVC TerminateProcess)
51 if (status == ThreadStatus::Ready || status == ThreadStatus::Paused) {
52 scheduler->UnscheduleThread(this, current_priority);
53 }
54
55 status = ThreadStatus::Dead;
56
57 WakeupAllWaitingThreads(); 49 WakeupAllWaitingThreads();
58 50
59 // Clean up any dangling references in objects that this thread was waiting for 51 // Clean up any dangling references in objects that this thread was waiting for
@@ -132,17 +124,16 @@ void Thread::ResumeFromWait() {
132 wakeup_callback = nullptr; 124 wakeup_callback = nullptr;
133 125
134 if (activity == ThreadActivity::Paused) { 126 if (activity == ThreadActivity::Paused) {
135 status = ThreadStatus::Paused; 127 SetStatus(ThreadStatus::Paused);
136 return; 128 return;
137 } 129 }
138 130
139 status = ThreadStatus::Ready; 131 SetStatus(ThreadStatus::Ready);
140
141 ChangeScheduler();
142} 132}
143 133
144void Thread::CancelWait() { 134void Thread::CancelWait() {
145 ASSERT(GetStatus() == ThreadStatus::WaitSynch); 135 ASSERT(GetStatus() == ThreadStatus::WaitSynch);
136 ClearWaitObjects();
146 SetWaitSynchronizationResult(ERR_SYNCHRONIZATION_CANCELED); 137 SetWaitSynchronizationResult(ERR_SYNCHRONIZATION_CANCELED);
147 ResumeFromWait(); 138 ResumeFromWait();
148} 139}
@@ -205,9 +196,9 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
205 thread->name = std::move(name); 196 thread->name = std::move(name);
206 thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap(); 197 thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
207 thread->owner_process = &owner_process; 198 thread->owner_process = &owner_process;
199 auto& scheduler = kernel.GlobalScheduler();
200 scheduler.AddThread(thread);
208 thread->tls_address = thread->owner_process->CreateTLSRegion(); 201 thread->tls_address = thread->owner_process->CreateTLSRegion();
209 thread->scheduler = &system.Scheduler(processor_id);
210 thread->scheduler->AddThread(thread);
211 202
212 thread->owner_process->RegisterThread(thread.get()); 203 thread->owner_process->RegisterThread(thread.get());
213 204
@@ -250,6 +241,22 @@ void Thread::SetStatus(ThreadStatus new_status) {
250 return; 241 return;
251 } 242 }
252 243
244 switch (new_status) {
245 case ThreadStatus::Ready:
246 case ThreadStatus::Running:
247 SetSchedulingStatus(ThreadSchedStatus::Runnable);
248 break;
249 case ThreadStatus::Dormant:
250 SetSchedulingStatus(ThreadSchedStatus::None);
251 break;
252 case ThreadStatus::Dead:
253 SetSchedulingStatus(ThreadSchedStatus::Exited);
254 break;
255 default:
256 SetSchedulingStatus(ThreadSchedStatus::Paused);
257 break;
258 }
259
253 if (status == ThreadStatus::Running) { 260 if (status == ThreadStatus::Running) {
254 last_running_ticks = Core::System::GetInstance().CoreTiming().GetTicks(); 261 last_running_ticks = Core::System::GetInstance().CoreTiming().GetTicks();
255 } 262 }
@@ -311,8 +318,7 @@ void Thread::UpdatePriority() {
311 return; 318 return;
312 } 319 }
313 320
314 scheduler->SetThreadPriority(this, new_priority); 321 SetCurrentPriority(new_priority);
315 current_priority = new_priority;
316 322
317 if (!lock_owner) { 323 if (!lock_owner) {
318 return; 324 return;
@@ -328,47 +334,7 @@ void Thread::UpdatePriority() {
328} 334}
329 335
330void Thread::ChangeCore(u32 core, u64 mask) { 336void Thread::ChangeCore(u32 core, u64 mask) {
331 ideal_core = core; 337 SetCoreAndAffinityMask(core, mask);
332 affinity_mask = mask;
333 ChangeScheduler();
334}
335
336void Thread::ChangeScheduler() {
337 if (status != ThreadStatus::Ready) {
338 return;
339 }
340
341 auto& system = Core::System::GetInstance();
342 std::optional<s32> new_processor_id{GetNextProcessorId(affinity_mask)};
343
344 if (!new_processor_id) {
345 new_processor_id = processor_id;
346 }
347 if (ideal_core != -1 && system.Scheduler(ideal_core).GetCurrentThread() == nullptr) {
348 new_processor_id = ideal_core;
349 }
350
351 ASSERT(*new_processor_id < 4);
352
353 // Add thread to new core's scheduler
354 auto& next_scheduler = system.Scheduler(*new_processor_id);
355
356 if (*new_processor_id != processor_id) {
357 // Remove thread from previous core's scheduler
358 scheduler->RemoveThread(this);
359 next_scheduler.AddThread(this);
360 }
361
362 processor_id = *new_processor_id;
363
364 // If the thread was ready, unschedule from the previous core and schedule on the new core
365 scheduler->UnscheduleThread(this, current_priority);
366 next_scheduler.ScheduleThread(this, current_priority);
367
368 // Change thread's scheduler
369 scheduler = &next_scheduler;
370
371 system.CpuCore(processor_id).PrepareReschedule();
372} 338}
373 339
374bool Thread::AllWaitObjectsReady() const { 340bool Thread::AllWaitObjectsReady() const {
@@ -388,10 +354,8 @@ void Thread::SetActivity(ThreadActivity value) {
388 354
389 if (value == ThreadActivity::Paused) { 355 if (value == ThreadActivity::Paused) {
390 // Set status if not waiting 356 // Set status if not waiting
391 if (status == ThreadStatus::Ready) { 357 if (status == ThreadStatus::Ready || status == ThreadStatus::Running) {
392 status = ThreadStatus::Paused; 358 SetStatus(ThreadStatus::Paused);
393 } else if (status == ThreadStatus::Running) {
394 status = ThreadStatus::Paused;
395 Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule(); 359 Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
396 } 360 }
397 } else if (status == ThreadStatus::Paused) { 361 } else if (status == ThreadStatus::Paused) {
@@ -408,6 +372,170 @@ void Thread::Sleep(s64 nanoseconds) {
408 WakeAfterDelay(nanoseconds); 372 WakeAfterDelay(nanoseconds);
409} 373}
410 374
375bool Thread::YieldSimple() {
376 auto& scheduler = kernel.GlobalScheduler();
377 return scheduler.YieldThread(this);
378}
379
380bool Thread::YieldAndBalanceLoad() {
381 auto& scheduler = kernel.GlobalScheduler();
382 return scheduler.YieldThreadAndBalanceLoad(this);
383}
384
385bool Thread::YieldAndWaitForLoadBalancing() {
386 auto& scheduler = kernel.GlobalScheduler();
387 return scheduler.YieldThreadAndWaitForLoadBalancing(this);
388}
389
390void Thread::SetSchedulingStatus(ThreadSchedStatus new_status) {
391 const u32 old_flags = scheduling_state;
392 scheduling_state = (scheduling_state & static_cast<u32>(ThreadSchedMasks::HighMask)) |
393 static_cast<u32>(new_status);
394 AdjustSchedulingOnStatus(old_flags);
395}
396
397void Thread::SetCurrentPriority(u32 new_priority) {
398 const u32 old_priority = std::exchange(current_priority, new_priority);
399 AdjustSchedulingOnPriority(old_priority);
400}
401
402ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) {
403 const auto HighestSetCore = [](u64 mask, u32 max_cores) {
404 for (s32 core = max_cores - 1; core >= 0; core--) {
405 if (((mask >> core) & 1) != 0) {
406 return core;
407 }
408 }
409 return -1;
410 };
411
412 const bool use_override = affinity_override_count != 0;
413 if (new_core == THREADPROCESSORID_DONT_UPDATE) {
414 new_core = use_override ? ideal_core_override : ideal_core;
415 if ((new_affinity_mask & (1ULL << new_core)) == 0) {
416 return ERR_INVALID_COMBINATION;
417 }
418 }
419 if (use_override) {
420 ideal_core_override = new_core;
421 affinity_mask_override = new_affinity_mask;
422 } else {
423 const u64 old_affinity_mask = std::exchange(affinity_mask, new_affinity_mask);
424 ideal_core = new_core;
425 if (old_affinity_mask != new_affinity_mask) {
426 const s32 old_core = processor_id;
427 if (processor_id >= 0 && ((affinity_mask >> processor_id) & 1) == 0) {
428 if (ideal_core < 0) {
429 processor_id = HighestSetCore(affinity_mask, GlobalScheduler::NUM_CPU_CORES);
430 } else {
431 processor_id = ideal_core;
432 }
433 }
434 AdjustSchedulingOnAffinity(old_affinity_mask, old_core);
435 }
436 }
437 return RESULT_SUCCESS;
438}
439
440void Thread::AdjustSchedulingOnStatus(u32 old_flags) {
441 if (old_flags == scheduling_state) {
442 return;
443 }
444
445 auto& scheduler = kernel.GlobalScheduler();
446 if (static_cast<ThreadSchedStatus>(old_flags & static_cast<u32>(ThreadSchedMasks::LowMask)) ==
447 ThreadSchedStatus::Runnable) {
448 // In this case the thread was running, now it's pausing/exitting
449 if (processor_id >= 0) {
450 scheduler.Unschedule(current_priority, processor_id, this);
451 }
452
453 for (s32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) {
454 if (core != processor_id && ((affinity_mask >> core) & 1) != 0) {
455 scheduler.Unsuggest(current_priority, static_cast<u32>(core), this);
456 }
457 }
458 } else if (GetSchedulingStatus() == ThreadSchedStatus::Runnable) {
459 // The thread is now set to running from being stopped
460 if (processor_id >= 0) {
461 scheduler.Schedule(current_priority, processor_id, this);
462 }
463
464 for (s32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) {
465 if (core != processor_id && ((affinity_mask >> core) & 1) != 0) {
466 scheduler.Suggest(current_priority, static_cast<u32>(core), this);
467 }
468 }
469 }
470
471 scheduler.SetReselectionPending();
472}
473
474void Thread::AdjustSchedulingOnPriority(u32 old_priority) {
475 if (GetSchedulingStatus() != ThreadSchedStatus::Runnable) {
476 return;
477 }
478 auto& scheduler = Core::System::GetInstance().GlobalScheduler();
479 if (processor_id >= 0) {
480 scheduler.Unschedule(old_priority, processor_id, this);
481 }
482
483 for (u32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) {
484 if (core != processor_id && ((affinity_mask >> core) & 1) != 0) {
485 scheduler.Unsuggest(old_priority, core, this);
486 }
487 }
488
489 // Add thread to the new priority queues.
490 Thread* current_thread = GetCurrentThread();
491
492 if (processor_id >= 0) {
493 if (current_thread == this) {
494 scheduler.SchedulePrepend(current_priority, processor_id, this);
495 } else {
496 scheduler.Schedule(current_priority, processor_id, this);
497 }
498 }
499
500 for (u32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) {
501 if (core != processor_id && ((affinity_mask >> core) & 1) != 0) {
502 scheduler.Suggest(current_priority, core, this);
503 }
504 }
505
506 scheduler.SetReselectionPending();
507}
508
509void Thread::AdjustSchedulingOnAffinity(u64 old_affinity_mask, s32 old_core) {
510 auto& scheduler = Core::System::GetInstance().GlobalScheduler();
511 if (GetSchedulingStatus() != ThreadSchedStatus::Runnable ||
512 current_priority >= THREADPRIO_COUNT) {
513 return;
514 }
515
516 for (u32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) {
517 if (((old_affinity_mask >> core) & 1) != 0) {
518 if (core == old_core) {
519 scheduler.Unschedule(current_priority, core, this);
520 } else {
521 scheduler.Unsuggest(current_priority, core, this);
522 }
523 }
524 }
525
526 for (u32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) {
527 if (((affinity_mask >> core) & 1) != 0) {
528 if (core == processor_id) {
529 scheduler.Schedule(current_priority, core, this);
530 } else {
531 scheduler.Suggest(current_priority, core, this);
532 }
533 }
534 }
535
536 scheduler.SetReselectionPending();
537}
538
411//////////////////////////////////////////////////////////////////////////////////////////////////// 539////////////////////////////////////////////////////////////////////////////////////////////////////
412 540
413/** 541/**
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 07e989637..c9870873d 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -75,6 +75,26 @@ enum class ThreadActivity : u32 {
75 Paused = 1, 75 Paused = 1,
76}; 76};
77 77
78enum class ThreadSchedStatus : u32 {
79 None = 0,
80 Paused = 1,
81 Runnable = 2,
82 Exited = 3,
83};
84
85enum class ThreadSchedFlags : u32 {
86 ProcessPauseFlag = 1 << 4,
87 ThreadPauseFlag = 1 << 5,
88 ProcessDebugPauseFlag = 1 << 6,
89 KernelInitPauseFlag = 1 << 8,
90};
91
92enum class ThreadSchedMasks : u32 {
93 LowMask = 0x000f,
94 HighMask = 0xfff0,
95 ForcePauseMask = 0x0070,
96};
97
78class Thread final : public WaitObject { 98class Thread final : public WaitObject {
79public: 99public:
80 using MutexWaitingThreads = std::vector<SharedPtr<Thread>>; 100 using MutexWaitingThreads = std::vector<SharedPtr<Thread>>;
@@ -278,6 +298,10 @@ public:
278 return processor_id; 298 return processor_id;
279 } 299 }
280 300
301 void SetProcessorID(s32 new_core) {
302 processor_id = new_core;
303 }
304
281 Process* GetOwnerProcess() { 305 Process* GetOwnerProcess() {
282 return owner_process; 306 return owner_process;
283 } 307 }
@@ -295,6 +319,9 @@ public:
295 } 319 }
296 320
297 void ClearWaitObjects() { 321 void ClearWaitObjects() {
322 for (const auto& waiting_object : wait_objects) {
323 waiting_object->RemoveWaitingThread(this);
324 }
298 wait_objects.clear(); 325 wait_objects.clear();
299 } 326 }
300 327
@@ -383,11 +410,47 @@ public:
383 /// Sleeps this thread for the given amount of nanoseconds. 410 /// Sleeps this thread for the given amount of nanoseconds.
384 void Sleep(s64 nanoseconds); 411 void Sleep(s64 nanoseconds);
385 412
413 /// Yields this thread without rebalancing loads.
414 bool YieldSimple();
415
416 /// Yields this thread and does a load rebalancing.
417 bool YieldAndBalanceLoad();
418
419 /// Yields this thread and if the core is left idle, loads are rebalanced
420 bool YieldAndWaitForLoadBalancing();
421
422 void IncrementYieldCount() {
423 yield_count++;
424 }
425
426 u64 GetYieldCount() const {
427 return yield_count;
428 }
429
430 ThreadSchedStatus GetSchedulingStatus() const {
431 return static_cast<ThreadSchedStatus>(scheduling_state &
432 static_cast<u32>(ThreadSchedMasks::LowMask));
433 }
434
435 bool IsRunning() const {
436 return is_running;
437 }
438
439 void SetIsRunning(bool value) {
440 is_running = value;
441 }
442
386private: 443private:
387 explicit Thread(KernelCore& kernel); 444 explicit Thread(KernelCore& kernel);
388 ~Thread() override; 445 ~Thread() override;
389 446
390 void ChangeScheduler(); 447 void SetSchedulingStatus(ThreadSchedStatus new_status);
448 void SetCurrentPriority(u32 new_priority);
449 ResultCode SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask);
450
451 void AdjustSchedulingOnStatus(u32 old_flags);
452 void AdjustSchedulingOnPriority(u32 old_priority);
453 void AdjustSchedulingOnAffinity(u64 old_affinity_mask, s32 old_core);
391 454
392 Core::ARM_Interface::ThreadContext context{}; 455 Core::ARM_Interface::ThreadContext context{};
393 456
@@ -409,6 +472,8 @@ private:
409 472
410 u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks. 473 u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks.
411 u64 last_running_ticks = 0; ///< CPU tick when thread was last running 474 u64 last_running_ticks = 0; ///< CPU tick when thread was last running
475 u64 yield_count = 0; ///< Number of redundant yields carried by this thread.
476 ///< a redundant yield is one where no scheduling is changed
412 477
413 s32 processor_id = 0; 478 s32 processor_id = 0;
414 479
@@ -453,6 +518,13 @@ private:
453 518
454 ThreadActivity activity = ThreadActivity::Normal; 519 ThreadActivity activity = ThreadActivity::Normal;
455 520
521 s32 ideal_core_override = -1;
522 u64 affinity_mask_override = 0x1;
523 u32 affinity_override_count = 0;
524
525 u32 scheduling_state = 0;
526 bool is_running = false;
527
456 std::string name; 528 std::string name;
457}; 529};
458 530
diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp
index 0e96ba872..c00cef062 100644
--- a/src/core/hle/kernel/wait_object.cpp
+++ b/src/core/hle/kernel/wait_object.cpp
@@ -6,6 +6,9 @@
6#include "common/assert.h" 6#include "common/assert.h"
7#include "common/common_types.h" 7#include "common/common_types.h"
8#include "common/logging/log.h" 8#include "common/logging/log.h"
9#include "core/core.h"
10#include "core/core_cpu.h"
11#include "core/hle/kernel/kernel.h"
9#include "core/hle/kernel/object.h" 12#include "core/hle/kernel/object.h"
10#include "core/hle/kernel/process.h" 13#include "core/hle/kernel/process.h"
11#include "core/hle/kernel/thread.h" 14#include "core/hle/kernel/thread.h"
@@ -82,9 +85,6 @@ void WaitObject::WakeupWaitingThread(SharedPtr<Thread> thread) {
82 85
83 const std::size_t index = thread->GetWaitObjectIndex(this); 86 const std::size_t index = thread->GetWaitObjectIndex(this);
84 87
85 for (const auto& object : thread->GetWaitObjects()) {
86 object->RemoveWaitingThread(thread.get());
87 }
88 thread->ClearWaitObjects(); 88 thread->ClearWaitObjects();
89 89
90 thread->CancelWakeupTimer(); 90 thread->CancelWakeupTimer();
@@ -95,6 +95,7 @@ void WaitObject::WakeupWaitingThread(SharedPtr<Thread> thread) {
95 } 95 }
96 if (resume) { 96 if (resume) {
97 thread->ResumeFromWait(); 97 thread->ResumeFromWait();
98 Core::System::GetInstance().PrepareReschedule(thread->GetProcessorID());
98 } 99 }
99} 100}
100 101
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index cd8180f8b..c5b9aa08f 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -66,10 +66,7 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList()
66 }; 66 };
67 67
68 const auto& system = Core::System::GetInstance(); 68 const auto& system = Core::System::GetInstance();
69 add_threads(system.Scheduler(0).GetThreadList()); 69 add_threads(system.GlobalScheduler().GetThreadList());
70 add_threads(system.Scheduler(1).GetThreadList());
71 add_threads(system.Scheduler(2).GetThreadList());
72 add_threads(system.Scheduler(3).GetThreadList());
73 70
74 return item_list; 71 return item_list;
75} 72}