summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar bunnei2018-05-11 12:59:23 -0400
committerGravatar GitHub2018-05-11 12:59:23 -0400
commit1b5c02fc37206bbd33715d2dde6258c3f835581c (patch)
tree1c33c66e734ff55228e4293cd2720070cd467080
parentMerge pull request #439 from ogniK5377/GetTPCMasks (diff)
parentcore: Add several missing docstrings. (diff)
downloadyuzu-1b5c02fc37206bbd33715d2dde6258c3f835581c.tar.gz
yuzu-1b5c02fc37206bbd33715d2dde6258c3f835581c.tar.xz
yuzu-1b5c02fc37206bbd33715d2dde6258c3f835581c.zip
Merge pull request #436 from bunnei/multi-core
Initial support for multi-core
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/arm/unicorn/arm_unicorn.cpp2
-rw-r--r--src/core/core.cpp117
-rw-r--r--src/core/core.h56
-rw-r--r--src/core/core_cpu.cpp119
-rw-r--r--src/core/core_cpu.h78
-rw-r--r--src/core/gdbstub/gdbstub.cpp24
-rw-r--r--src/core/hle/kernel/scheduler.cpp15
-rw-r--r--src/core/hle/kernel/scheduler.h3
-rw-r--r--src/core/hle/kernel/svc.cpp171
-rw-r--r--src/core/hle/kernel/svc_wrap.h24
-rw-r--r--src/core/hle/kernel/thread.cpp102
-rw-r--r--src/core/hle/kernel/thread.h10
-rw-r--r--src/core/hle/kernel/vm_manager.cpp23
-rw-r--r--src/core/memory.cpp9
-rw-r--r--src/core/settings.h1
-rw-r--r--src/core/telemetry_session.cpp2
-rw-r--r--src/yuzu/configuration/config.cpp2
-rw-r--r--src/yuzu/configuration/configure_general.cpp3
-rw-r--r--src/yuzu/configuration/configure_general.ui7
-rw-r--r--src/yuzu/debugger/registers.cpp4
-rw-r--r--src/yuzu/debugger/wait_tree.cpp23
-rw-r--r--src/yuzu_cmd/config.cpp1
-rw-r--r--src/yuzu_cmd/default_ini.h4
24 files changed, 613 insertions, 189 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index a41e22f4a..821d2f883 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -4,6 +4,8 @@ add_library(core STATIC
4 arm/unicorn/arm_unicorn.h 4 arm/unicorn/arm_unicorn.h
5 core.cpp 5 core.cpp
6 core.h 6 core.h
7 core_cpu.cpp
8 core_cpu.h
7 core_timing.cpp 9 core_timing.cpp
8 core_timing.h 10 core_timing.h
9 file_sys/directory.h 11 file_sys/directory.h
diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp
index 574922130..c0cc62f03 100644
--- a/src/core/arm/unicorn/arm_unicorn.cpp
+++ b/src/core/arm/unicorn/arm_unicorn.cpp
@@ -52,7 +52,7 @@ static void InterruptHook(uc_engine* uc, u32 intNo, void* user_data) {
52static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value, 52static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value,
53 void* user_data) { 53 void* user_data) {
54 ARM_Interface::ThreadContext ctx{}; 54 ARM_Interface::ThreadContext ctx{};
55 Core::CPU().SaveContext(ctx); 55 Core::CurrentArmInterface().SaveContext(ctx);
56 ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x{:X}, pc=0x{:X}, lr=0x{:X}", addr, 56 ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x{:X}, pc=0x{:X}, lr=0x{:X}", addr,
57 ctx.pc, ctx.cpu_registers[30]); 57 ctx.pc, ctx.cpu_registers[30]);
58 return {}; 58 return {};
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 9e2229d02..84ab876cc 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -5,10 +5,6 @@
5#include <memory> 5#include <memory>
6#include <utility> 6#include <utility>
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8#ifdef ARCHITECTURE_x86_64
9#include "core/arm/dynarmic/arm_dynarmic.h"
10#endif
11#include "core/arm/unicorn/arm_unicorn.h"
12#include "core/core.h" 8#include "core/core.h"
13#include "core/core_timing.h" 9#include "core/core_timing.h"
14#include "core/gdbstub/gdbstub.h" 10#include "core/gdbstub/gdbstub.h"
@@ -31,11 +27,31 @@ namespace Core {
31 27
32System::~System() = default; 28System::~System() = default;
33 29
30/// Runs a CPU core while the system is powered on
31static void RunCpuCore(std::shared_ptr<Cpu> cpu_state) {
32 while (Core::System().GetInstance().IsPoweredOn()) {
33 cpu_state->RunLoop(true);
34 }
35}
36
37Cpu& System::CurrentCpuCore() {
38 // If multicore is enabled, use host thread to figure out the current CPU core
39 if (Settings::values.use_multi_core) {
40 const auto& search = thread_to_cpu.find(std::this_thread::get_id());
41 ASSERT(search != thread_to_cpu.end());
42 ASSERT(search->second);
43 return *search->second;
44 }
45
46 // Otherwise, use single-threaded mode active_core variable
47 return *cpu_cores[active_core];
48}
49
34System::ResultStatus System::RunLoop(bool tight_loop) { 50System::ResultStatus System::RunLoop(bool tight_loop) {
35 status = ResultStatus::Success; 51 status = ResultStatus::Success;
36 if (!cpu_core) { 52
37 return ResultStatus::ErrorNotInitialized; 53 // Update thread_to_cpu in case Core 0 is run from a different host thread
38 } 54 thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
39 55
40 if (GDBStub::IsServerEnabled()) { 56 if (GDBStub::IsServerEnabled()) {
41 GDBStub::HandlePacket(); 57 GDBStub::HandlePacket();
@@ -52,25 +68,14 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
52 } 68 }
53 } 69 }
54 70
55 // If we don't have a currently active thread then don't execute instructions, 71 for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) {
56 // instead advance to the next event and try to yield to the next thread 72 cpu_cores[active_core]->RunLoop(tight_loop);
57 if (Kernel::GetCurrentThread() == nullptr) { 73 if (Settings::values.use_multi_core) {
58 NGLOG_TRACE(Core_ARM, "Idling"); 74 // Cores 1-3 are run on other threads in this mode
59 CoreTiming::Idle(); 75 break;
60 CoreTiming::Advance();
61 PrepareReschedule();
62 } else {
63 CoreTiming::Advance();
64 if (tight_loop) {
65 cpu_core->Run();
66 } else {
67 cpu_core->Step();
68 } 76 }
69 } 77 }
70 78
71 HW::Update();
72 Reschedule();
73
74 return status; 79 return status;
75} 80}
76 81
@@ -133,21 +138,26 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
133} 138}
134 139
135void System::PrepareReschedule() { 140void System::PrepareReschedule() {
136 cpu_core->PrepareReschedule(); 141 CurrentCpuCore().PrepareReschedule();
137 reschedule_pending = true;
138} 142}
139 143
140PerfStats::Results System::GetAndResetPerfStats() { 144PerfStats::Results System::GetAndResetPerfStats() {
141 return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs()); 145 return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs());
142} 146}
143 147
144void System::Reschedule() { 148const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) {
145 if (!reschedule_pending) { 149 ASSERT(core_index < NUM_CPU_CORES);
146 return; 150 return cpu_cores[core_index]->Scheduler();
147 } 151}
148 152
149 reschedule_pending = false; 153ARM_Interface& System::ArmInterface(size_t core_index) {
150 Core::System::GetInstance().Scheduler().Reschedule(); 154 ASSERT(core_index < NUM_CPU_CORES);
155 return cpu_cores[core_index]->ArmInterface();
156}
157
158Cpu& System::CpuCore(size_t core_index) {
159 ASSERT(core_index < NUM_CPU_CORES);
160 return *cpu_cores[core_index];
151} 161}
152 162
153System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { 163System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
@@ -157,26 +167,17 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
157 167
158 current_process = Kernel::Process::Create("main"); 168 current_process = Kernel::Process::Create("main");
159 169
160 if (Settings::values.use_cpu_jit) { 170 cpu_barrier = std::make_shared<CpuBarrier>();
161#ifdef ARCHITECTURE_x86_64 171 for (size_t index = 0; index < cpu_cores.size(); ++index) {
162 cpu_core = std::make_shared<ARM_Dynarmic>(); 172 cpu_cores[index] = std::make_shared<Cpu>(cpu_barrier, index);
163#else
164 cpu_core = std::make_shared<ARM_Unicorn>();
165 NGLOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
166#endif
167 } else {
168 cpu_core = std::make_shared<ARM_Unicorn>();
169 } 173 }
170 174
171 gpu_core = std::make_unique<Tegra::GPU>(); 175 gpu_core = std::make_unique<Tegra::GPU>();
172
173 telemetry_session = std::make_unique<Core::TelemetrySession>(); 176 telemetry_session = std::make_unique<Core::TelemetrySession>();
174
175 service_manager = std::make_shared<Service::SM::ServiceManager>(); 177 service_manager = std::make_shared<Service::SM::ServiceManager>();
176 178
177 HW::Init(); 179 HW::Init();
178 Kernel::Init(system_mode); 180 Kernel::Init(system_mode);
179 scheduler = std::make_unique<Kernel::Scheduler>(cpu_core.get());
180 Service::Init(service_manager); 181 Service::Init(service_manager);
181 GDBStub::Init(); 182 GDBStub::Init();
182 183
@@ -184,6 +185,17 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
184 return ResultStatus::ErrorVideoCore; 185 return ResultStatus::ErrorVideoCore;
185 } 186 }
186 187
188 // Create threads for CPU cores 1-3, and build thread_to_cpu map
189 // CPU core 0 is run on the main thread
190 thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
191 if (Settings::values.use_multi_core) {
192 for (size_t index = 0; index < cpu_core_threads.size(); ++index) {
193 cpu_core_threads[index] =
194 std::make_unique<std::thread>(RunCpuCore, cpu_cores[index + 1]);
195 thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1];
196 }
197 }
198
187 NGLOG_DEBUG(Core, "Initialized OK"); 199 NGLOG_DEBUG(Core, "Initialized OK");
188 200
189 // Reset counters and set time origin to current frame 201 // Reset counters and set time origin to current frame
@@ -207,15 +219,30 @@ void System::Shutdown() {
207 VideoCore::Shutdown(); 219 VideoCore::Shutdown();
208 GDBStub::Shutdown(); 220 GDBStub::Shutdown();
209 Service::Shutdown(); 221 Service::Shutdown();
210 scheduler.reset();
211 Kernel::Shutdown(); 222 Kernel::Shutdown();
212 HW::Shutdown(); 223 HW::Shutdown();
213 service_manager.reset(); 224 service_manager.reset();
214 telemetry_session.reset(); 225 telemetry_session.reset();
215 gpu_core.reset(); 226 gpu_core.reset();
216 cpu_core.reset(); 227
228 // Close all CPU/threading state
229 cpu_barrier->NotifyEnd();
230 if (Settings::values.use_multi_core) {
231 for (auto& thread : cpu_core_threads) {
232 thread->join();
233 thread.reset();
234 }
235 }
236 thread_to_cpu.clear();
237 for (auto& cpu_core : cpu_cores) {
238 cpu_core.reset();
239 }
240 cpu_barrier.reset();
241
242 // Close core timing
217 CoreTiming::Shutdown(); 243 CoreTiming::Shutdown();
218 244
245 // Close app loader
219 app_loader.reset(); 246 app_loader.reset();
220 247
221 NGLOG_DEBUG(Core, "Shutdown OK"); 248 NGLOG_DEBUG(Core, "Shutdown OK");
diff --git a/src/core/core.h b/src/core/core.h
index f81cbfb3c..f90f085ad 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -4,9 +4,12 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <array>
7#include <memory> 8#include <memory>
8#include <string> 9#include <string>
10#include <thread>
9#include "common/common_types.h" 11#include "common/common_types.h"
12#include "core/core_cpu.h"
10#include "core/hle/kernel/kernel.h" 13#include "core/hle/kernel/kernel.h"
11#include "core/hle/kernel/scheduler.h" 14#include "core/hle/kernel/scheduler.h"
12#include "core/loader/loader.h" 15#include "core/loader/loader.h"
@@ -89,7 +92,7 @@ public:
89 * @returns True if the emulated system is powered on, otherwise false. 92 * @returns True if the emulated system is powered on, otherwise false.
90 */ 93 */
91 bool IsPoweredOn() const { 94 bool IsPoweredOn() const {
92 return cpu_core != nullptr; 95 return cpu_barrier && cpu_barrier->IsAlive();
93 } 96 }
94 97
95 /** 98 /**
@@ -103,24 +106,34 @@ public:
103 /// Prepare the core emulation for a reschedule 106 /// Prepare the core emulation for a reschedule
104 void PrepareReschedule(); 107 void PrepareReschedule();
105 108
109 /// Gets and resets core performance statistics
106 PerfStats::Results GetAndResetPerfStats(); 110 PerfStats::Results GetAndResetPerfStats();
107 111
108 /** 112 /// Gets an ARM interface to the CPU core that is currently running
109 * Gets a reference to the emulated CPU. 113 ARM_Interface& CurrentArmInterface() {
110 * @returns A reference to the emulated CPU. 114 return CurrentCpuCore().ArmInterface();
111 */
112 ARM_Interface& CPU() {
113 return *cpu_core;
114 } 115 }
115 116
117 /// Gets an ARM interface to the CPU core with the specified index
118 ARM_Interface& ArmInterface(size_t core_index);
119
120 /// Gets a CPU interface to the CPU core with the specified index
121 Cpu& CpuCore(size_t core_index);
122
123 /// Gets the GPU interface
116 Tegra::GPU& GPU() { 124 Tegra::GPU& GPU() {
117 return *gpu_core; 125 return *gpu_core;
118 } 126 }
119 127
120 Kernel::Scheduler& Scheduler() { 128 /// Gets the scheduler for the CPU core that is currently running
121 return *scheduler; 129 Kernel::Scheduler& CurrentScheduler() {
130 return *CurrentCpuCore().Scheduler();
122 } 131 }
123 132
133 /// Gets the scheduler for the CPU core with the specified index
134 const std::shared_ptr<Kernel::Scheduler>& Scheduler(size_t core_index);
135
136 /// Gets the current process
124 Kernel::SharedPtr<Kernel::Process>& CurrentProcess() { 137 Kernel::SharedPtr<Kernel::Process>& CurrentProcess() {
125 return current_process; 138 return current_process;
126 } 139 }
@@ -155,6 +168,9 @@ public:
155 } 168 }
156 169
157private: 170private:
171 /// Returns the currently running CPU core
172 Cpu& CurrentCpuCore();
173
158 /** 174 /**
159 * Initialize the emulated system. 175 * Initialize the emulated system.
160 * @param emu_window Pointer to the host-system window used for video output and keyboard input. 176 * @param emu_window Pointer to the host-system window used for video output and keyboard input.
@@ -163,22 +179,15 @@ private:
163 */ 179 */
164 ResultStatus Init(EmuWindow* emu_window, u32 system_mode); 180 ResultStatus Init(EmuWindow* emu_window, u32 system_mode);
165 181
166 /// Reschedule the core emulation
167 void Reschedule();
168
169 /// AppLoader used to load the current executing application 182 /// AppLoader used to load the current executing application
170 std::unique_ptr<Loader::AppLoader> app_loader; 183 std::unique_ptr<Loader::AppLoader> app_loader;
171
172 std::shared_ptr<ARM_Interface> cpu_core;
173 std::unique_ptr<Kernel::Scheduler> scheduler;
174 std::unique_ptr<Tegra::GPU> gpu_core; 184 std::unique_ptr<Tegra::GPU> gpu_core;
175
176 std::shared_ptr<Tegra::DebugContext> debug_context; 185 std::shared_ptr<Tegra::DebugContext> debug_context;
177
178 Kernel::SharedPtr<Kernel::Process> current_process; 186 Kernel::SharedPtr<Kernel::Process> current_process;
179 187 std::shared_ptr<CpuBarrier> cpu_barrier;
180 /// When true, signals that a reschedule should happen 188 std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
181 bool reschedule_pending{}; 189 std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads;
190 size_t active_core{}; ///< Active core, only used in single thread mode
182 191
183 /// Service manager 192 /// Service manager
184 std::shared_ptr<Service::SM::ServiceManager> service_manager; 193 std::shared_ptr<Service::SM::ServiceManager> service_manager;
@@ -190,10 +199,13 @@ private:
190 199
191 ResultStatus status = ResultStatus::Success; 200 ResultStatus status = ResultStatus::Success;
192 std::string status_details = ""; 201 std::string status_details = "";
202
203 /// Map of guest threads to CPU cores
204 std::map<std::thread::id, std::shared_ptr<Cpu>> thread_to_cpu;
193}; 205};
194 206
195inline ARM_Interface& CPU() { 207inline ARM_Interface& CurrentArmInterface() {
196 return System::GetInstance().CPU(); 208 return System::GetInstance().CurrentArmInterface();
197} 209}
198 210
199inline TelemetrySession& Telemetry() { 211inline TelemetrySession& Telemetry() {
diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp
new file mode 100644
index 000000000..099f2bb1a
--- /dev/null
+++ b/src/core/core_cpu.cpp
@@ -0,0 +1,119 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <condition_variable>
6#include <mutex>
7
8#include "common/logging/log.h"
9#ifdef ARCHITECTURE_x86_64
10#include "core/arm/dynarmic/arm_dynarmic.h"
11#endif
12#include "core/arm/unicorn/arm_unicorn.h"
13#include "core/core_cpu.h"
14#include "core/core_timing.h"
15#include "core/hle/kernel/kernel.h"
16#include "core/hle/kernel/scheduler.h"
17#include "core/hle/kernel/thread.h"
18#include "core/settings.h"
19
20namespace Core {
21
22void CpuBarrier::NotifyEnd() {
23 std::unique_lock<std::mutex> lock(mutex);
24 end = true;
25 condition.notify_all();
26}
27
28bool CpuBarrier::Rendezvous() {
29 if (!Settings::values.use_multi_core) {
30 // Meaningless when running in single-core mode
31 return true;
32 }
33
34 if (!end) {
35 std::unique_lock<std::mutex> lock(mutex);
36
37 --cores_waiting;
38 if (!cores_waiting) {
39 cores_waiting = NUM_CPU_CORES;
40 condition.notify_all();
41 return true;
42 }
43
44 condition.wait(lock);
45 return true;
46 }
47
48 return false;
49}
50
51Cpu::Cpu(std::shared_ptr<CpuBarrier> cpu_barrier, size_t core_index)
52 : cpu_barrier{std::move(cpu_barrier)}, core_index{core_index} {
53
54 if (Settings::values.use_cpu_jit) {
55#ifdef ARCHITECTURE_x86_64
56 arm_interface = std::make_shared<ARM_Dynarmic>();
57#else
58 cpu_core = std::make_shared<ARM_Unicorn>();
59 NGLOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
60#endif
61 } else {
62 arm_interface = std::make_shared<ARM_Unicorn>();
63 }
64
65 scheduler = std::make_shared<Kernel::Scheduler>(arm_interface.get());
66}
67
68void Cpu::RunLoop(bool tight_loop) {
69 // Wait for all other CPU cores to complete the previous slice, such that they run in lock-step
70 if (!cpu_barrier->Rendezvous()) {
71 // If rendezvous failed, session has been killed
72 return;
73 }
74
75 // If we don't have a currently active thread then don't execute instructions,
76 // instead advance to the next event and try to yield to the next thread
77 if (Kernel::GetCurrentThread() == nullptr) {
78 NGLOG_TRACE(Core, "Core-{} idling", core_index);
79
80 if (IsMainCore()) {
81 CoreTiming::Idle();
82 CoreTiming::Advance();
83 }
84
85 PrepareReschedule();
86 } else {
87 if (IsMainCore()) {
88 CoreTiming::Advance();
89 }
90
91 if (tight_loop) {
92 arm_interface->Run();
93 } else {
94 arm_interface->Step();
95 }
96 }
97
98 Reschedule();
99}
100
101void Cpu::SingleStep() {
102 return RunLoop(false);
103}
104
105void Cpu::PrepareReschedule() {
106 arm_interface->PrepareReschedule();
107 reschedule_pending = true;
108}
109
110void Cpu::Reschedule() {
111 if (!reschedule_pending) {
112 return;
113 }
114
115 reschedule_pending = false;
116 scheduler->Reschedule();
117}
118
119} // namespace Core
diff --git a/src/core/core_cpu.h b/src/core/core_cpu.h
new file mode 100644
index 000000000..243f0b5e7
--- /dev/null
+++ b/src/core/core_cpu.h
@@ -0,0 +1,78 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <atomic>
8#include <condition_variable>
9#include <memory>
10#include <mutex>
11#include <string>
12#include "common/common_types.h"
13
14class ARM_Interface;
15
16namespace Kernel {
17class Scheduler;
18}
19
20namespace Core {
21
22constexpr unsigned NUM_CPU_CORES{4};
23
24class CpuBarrier {
25public:
26 bool IsAlive() const {
27 return !end;
28 }
29
30 void NotifyEnd();
31
32 bool Rendezvous();
33
34private:
35 unsigned cores_waiting{NUM_CPU_CORES};
36 std::mutex mutex;
37 std::condition_variable condition;
38 std::atomic<bool> end{};
39};
40
41class Cpu {
42public:
43 Cpu(std::shared_ptr<CpuBarrier> cpu_barrier, size_t core_index);
44
45 void RunLoop(bool tight_loop = true);
46
47 void SingleStep();
48
49 void PrepareReschedule();
50
51 ARM_Interface& ArmInterface() {
52 return *arm_interface;
53 }
54
55 const ARM_Interface& ArmInterface() const {
56 return *arm_interface;
57 }
58
59 const std::shared_ptr<Kernel::Scheduler>& Scheduler() const {
60 return scheduler;
61 }
62
63 bool IsMainCore() const {
64 return core_index == 0;
65 }
66
67private:
68 void Reschedule();
69
70 std::shared_ptr<ARM_Interface> arm_interface;
71 std::shared_ptr<CpuBarrier> cpu_barrier;
72 std::shared_ptr<Kernel::Scheduler> scheduler;
73
74 bool reschedule_pending{};
75 size_t core_index;
76};
77
78} // namespace Core
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index 46606b992..6c5a40ba8 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -598,11 +598,11 @@ static void ReadRegister() {
598 } 598 }
599 599
600 if (id <= SP_REGISTER) { 600 if (id <= SP_REGISTER) {
601 LongToGdbHex(reply, Core::CPU().GetReg(static_cast<int>(id))); 601 LongToGdbHex(reply, Core::CurrentArmInterface().GetReg(static_cast<int>(id)));
602 } else if (id == PC_REGISTER) { 602 } else if (id == PC_REGISTER) {
603 LongToGdbHex(reply, Core::CPU().GetPC()); 603 LongToGdbHex(reply, Core::CurrentArmInterface().GetPC());
604 } else if (id == CPSR_REGISTER) { 604 } else if (id == CPSR_REGISTER) {
605 IntToGdbHex(reply, Core::CPU().GetCPSR()); 605 IntToGdbHex(reply, Core::CurrentArmInterface().GetCPSR());
606 } else { 606 } else {
607 return SendReply("E01"); 607 return SendReply("E01");
608 } 608 }
@@ -618,16 +618,16 @@ static void ReadRegisters() {
618 u8* bufptr = buffer; 618 u8* bufptr = buffer;
619 619
620 for (int reg = 0; reg <= SP_REGISTER; reg++) { 620 for (int reg = 0; reg <= SP_REGISTER; reg++) {
621 LongToGdbHex(bufptr + reg * 16, Core::CPU().GetReg(reg)); 621 LongToGdbHex(bufptr + reg * 16, Core::CurrentArmInterface().GetReg(reg));
622 } 622 }
623 623
624 bufptr += (32 * 16); 624 bufptr += (32 * 16);
625 625
626 LongToGdbHex(bufptr, Core::CPU().GetPC()); 626 LongToGdbHex(bufptr, Core::CurrentArmInterface().GetPC());
627 627
628 bufptr += 16; 628 bufptr += 16;
629 629
630 IntToGdbHex(bufptr, Core::CPU().GetCPSR()); 630 IntToGdbHex(bufptr, Core::CurrentArmInterface().GetCPSR());
631 631
632 bufptr += 8; 632 bufptr += 8;
633 633
@@ -646,11 +646,11 @@ static void WriteRegister() {
646 } 646 }
647 647
648 if (id <= SP_REGISTER) { 648 if (id <= SP_REGISTER) {
649 Core::CPU().SetReg(id, GdbHexToLong(buffer_ptr)); 649 Core::CurrentArmInterface().SetReg(id, GdbHexToLong(buffer_ptr));
650 } else if (id == PC_REGISTER) { 650 } else if (id == PC_REGISTER) {
651 Core::CPU().SetPC(GdbHexToLong(buffer_ptr)); 651 Core::CurrentArmInterface().SetPC(GdbHexToLong(buffer_ptr));
652 } else if (id == CPSR_REGISTER) { 652 } else if (id == CPSR_REGISTER) {
653 Core::CPU().SetCPSR(GdbHexToInt(buffer_ptr)); 653 Core::CurrentArmInterface().SetCPSR(GdbHexToInt(buffer_ptr));
654 } else { 654 } else {
655 return SendReply("E01"); 655 return SendReply("E01");
656 } 656 }
@@ -667,11 +667,11 @@ static void WriteRegisters() {
667 667
668 for (int i = 0, reg = 0; reg <= CPSR_REGISTER; i++, reg++) { 668 for (int i = 0, reg = 0; reg <= CPSR_REGISTER; i++, reg++) {
669 if (reg <= SP_REGISTER) { 669 if (reg <= SP_REGISTER) {
670 Core::CPU().SetReg(reg, GdbHexToLong(buffer_ptr + i * 16)); 670 Core::CurrentArmInterface().SetReg(reg, GdbHexToLong(buffer_ptr + i * 16));
671 } else if (reg == PC_REGISTER) { 671 } else if (reg == PC_REGISTER) {
672 Core::CPU().SetPC(GdbHexToLong(buffer_ptr + i * 16)); 672 Core::CurrentArmInterface().SetPC(GdbHexToLong(buffer_ptr + i * 16));
673 } else if (reg == CPSR_REGISTER) { 673 } else if (reg == CPSR_REGISTER) {
674 Core::CPU().SetCPSR(GdbHexToInt(buffer_ptr + i * 16)); 674 Core::CurrentArmInterface().SetCPSR(GdbHexToInt(buffer_ptr + i * 16));
675 } else { 675 } else {
676 UNIMPLEMENTED(); 676 UNIMPLEMENTED();
677 } 677 }
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index ff6a0941a..9cb9e0e5c 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -9,6 +9,8 @@
9 9
10namespace Kernel { 10namespace Kernel {
11 11
12std::mutex Scheduler::scheduler_mutex;
13
12Scheduler::Scheduler(ARM_Interface* cpu_core) : cpu_core(cpu_core) {} 14Scheduler::Scheduler(ARM_Interface* cpu_core) : cpu_core(cpu_core) {}
13 15
14Scheduler::~Scheduler() { 16Scheduler::~Scheduler() {
@@ -18,6 +20,7 @@ Scheduler::~Scheduler() {
18} 20}
19 21
20bool Scheduler::HaveReadyThreads() { 22bool Scheduler::HaveReadyThreads() {
23 std::lock_guard<std::mutex> lock(scheduler_mutex);
21 return ready_queue.get_first() != nullptr; 24 return ready_queue.get_first() != nullptr;
22} 25}
23 26
@@ -90,6 +93,8 @@ void Scheduler::SwitchContext(Thread* new_thread) {
90} 93}
91 94
92void Scheduler::Reschedule() { 95void Scheduler::Reschedule() {
96 std::lock_guard<std::mutex> lock(scheduler_mutex);
97
93 Thread* cur = GetCurrentThread(); 98 Thread* cur = GetCurrentThread();
94 Thread* next = PopNextReadyThread(); 99 Thread* next = PopNextReadyThread();
95 100
@@ -105,26 +110,36 @@ void Scheduler::Reschedule() {
105} 110}
106 111
107void Scheduler::AddThread(SharedPtr<Thread> thread, u32 priority) { 112void Scheduler::AddThread(SharedPtr<Thread> thread, u32 priority) {
113 std::lock_guard<std::mutex> lock(scheduler_mutex);
114
108 thread_list.push_back(thread); 115 thread_list.push_back(thread);
109 ready_queue.prepare(priority); 116 ready_queue.prepare(priority);
110} 117}
111 118
112void Scheduler::RemoveThread(Thread* thread) { 119void Scheduler::RemoveThread(Thread* thread) {
120 std::lock_guard<std::mutex> lock(scheduler_mutex);
121
113 thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread), 122 thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
114 thread_list.end()); 123 thread_list.end());
115} 124}
116 125
117void Scheduler::ScheduleThread(Thread* thread, u32 priority) { 126void Scheduler::ScheduleThread(Thread* thread, u32 priority) {
127 std::lock_guard<std::mutex> lock(scheduler_mutex);
128
118 ASSERT(thread->status == THREADSTATUS_READY); 129 ASSERT(thread->status == THREADSTATUS_READY);
119 ready_queue.push_back(priority, thread); 130 ready_queue.push_back(priority, thread);
120} 131}
121 132
122void Scheduler::UnscheduleThread(Thread* thread, u32 priority) { 133void Scheduler::UnscheduleThread(Thread* thread, u32 priority) {
134 std::lock_guard<std::mutex> lock(scheduler_mutex);
135
123 ASSERT(thread->status == THREADSTATUS_READY); 136 ASSERT(thread->status == THREADSTATUS_READY);
124 ready_queue.remove(priority, thread); 137 ready_queue.remove(priority, thread);
125} 138}
126 139
127void Scheduler::SetThreadPriority(Thread* thread, u32 priority) { 140void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
141 std::lock_guard<std::mutex> lock(scheduler_mutex);
142
128 // If thread was ready, adjust queues 143 // If thread was ready, adjust queues
129 if (thread->status == THREADSTATUS_READY) 144 if (thread->status == THREADSTATUS_READY)
130 ready_queue.move(thread, thread->current_priority, priority); 145 ready_queue.move(thread, thread->current_priority, priority);
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
index 27d0247d6..a3b5fb8ca 100644
--- a/src/core/hle/kernel/scheduler.h
+++ b/src/core/hle/kernel/scheduler.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <mutex>
7#include <vector> 8#include <vector>
8#include "common/common_types.h" 9#include "common/common_types.h"
9#include "common/thread_queue_list.h" 10#include "common/thread_queue_list.h"
@@ -68,6 +69,8 @@ private:
68 SharedPtr<Thread> current_thread = nullptr; 69 SharedPtr<Thread> current_thread = nullptr;
69 70
70 ARM_Interface* cpu_core; 71 ARM_Interface* cpu_core;
72
73 static std::mutex scheduler_mutex;
71}; 74};
72 75
73} // namespace Kernel 76} // namespace Kernel
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 72b5c05f2..1ae530c90 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -401,8 +401,8 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
401 401
402/// Get which CPU core is executing the current thread 402/// Get which CPU core is executing the current thread
403static u32 GetCurrentProcessorNumber() { 403static u32 GetCurrentProcessorNumber() {
404 NGLOG_WARNING(Kernel_SVC, "(STUBBED) called, defaulting to processor 0"); 404 NGLOG_TRACE(Kernel_SVC, "called");
405 return 0; 405 return GetCurrentThread()->processor_id;
406} 406}
407 407
408static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size, 408static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size,
@@ -485,22 +485,28 @@ static void ExitProcess() {
485 485
486 Core::CurrentProcess()->status = ProcessStatus::Exited; 486 Core::CurrentProcess()->status = ProcessStatus::Exited;
487 487
488 // Stop all the process threads that are currently waiting for objects. 488 auto stop_threads = [](const std::vector<SharedPtr<Thread>>& thread_list) {
489 auto& thread_list = Core::System::GetInstance().Scheduler().GetThreadList(); 489 for (auto& thread : thread_list) {
490 for (auto& thread : thread_list) { 490 if (thread->owner_process != Core::CurrentProcess())
491 if (thread->owner_process != Core::CurrentProcess()) 491 continue;
492 continue;
493 492
494 if (thread == GetCurrentThread()) 493 if (thread == GetCurrentThread())
495 continue; 494 continue;
496 495
497 // TODO(Subv): When are the other running/ready threads terminated? 496 // TODO(Subv): When are the other running/ready threads terminated?
498 ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || 497 ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY ||
499 thread->status == THREADSTATUS_WAIT_SYNCH_ALL, 498 thread->status == THREADSTATUS_WAIT_SYNCH_ALL,
500 "Exiting processes with non-waiting threads is currently unimplemented"); 499 "Exiting processes with non-waiting threads is currently unimplemented");
501 500
502 thread->Stop(); 501 thread->Stop();
503 } 502 }
503 };
504
505 auto& system = Core::System::GetInstance();
506 stop_threads(system.Scheduler(0)->GetThreadList());
507 stop_threads(system.Scheduler(1)->GetThreadList());
508 stop_threads(system.Scheduler(2)->GetThreadList());
509 stop_threads(system.Scheduler(3)->GetThreadList());
504 510
505 // Kill the current thread 511 // Kill the current thread
506 GetCurrentThread()->Stop(); 512 GetCurrentThread()->Stop();
@@ -530,14 +536,9 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
530 536
531 switch (processor_id) { 537 switch (processor_id) {
532 case THREADPROCESSORID_0: 538 case THREADPROCESSORID_0:
533 break;
534 case THREADPROCESSORID_1: 539 case THREADPROCESSORID_1:
535 case THREADPROCESSORID_2: 540 case THREADPROCESSORID_2:
536 case THREADPROCESSORID_3: 541 case THREADPROCESSORID_3:
537 // TODO(bunnei): Implement support for other processor IDs
538 NGLOG_ERROR(Kernel_SVC,
539 "Newly created thread must run in another thread ({}), unimplemented.",
540 processor_id);
541 break; 542 break;
542 default: 543 default:
543 ASSERT_MSG(false, "Unsupported thread processor ID: {}", processor_id); 544 ASSERT_MSG(false, "Unsupported thread processor ID: {}", processor_id);
@@ -576,7 +577,7 @@ static ResultCode StartThread(Handle thread_handle) {
576 577
577/// Called when a thread exits 578/// Called when a thread exits
578static void ExitThread() { 579static void ExitThread() {
579 NGLOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", Core::CPU().GetPC()); 580 NGLOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", Core::CurrentArmInterface().GetPC());
580 581
581 ExitCurrentThread(); 582 ExitCurrentThread();
582 Core::System::GetInstance().PrepareReschedule(); 583 Core::System::GetInstance().PrepareReschedule();
@@ -588,7 +589,7 @@ static void SleepThread(s64 nanoseconds) {
588 589
589 // Don't attempt to yield execution if there are no available threads to run, 590 // Don't attempt to yield execution if there are no available threads to run,
590 // this way we avoid a useless reschedule to the idle thread. 591 // this way we avoid a useless reschedule to the idle thread.
591 if (nanoseconds == 0 && !Core::System::GetInstance().Scheduler().HaveReadyThreads()) 592 if (nanoseconds == 0 && !Core::System::GetInstance().CurrentScheduler().HaveReadyThreads())
592 return; 593 return;
593 594
594 // Sleep current thread and check for next thread to schedule 595 // Sleep current thread and check for next thread to schedule
@@ -624,7 +625,7 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
624 625
625 // Note: Deliberately don't attempt to inherit the lock owner's priority. 626 // Note: Deliberately don't attempt to inherit the lock owner's priority.
626 627
627 Core::System::GetInstance().PrepareReschedule(); 628 Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule();
628 return RESULT_SUCCESS; 629 return RESULT_SUCCESS;
629} 630}
630 631
@@ -634,53 +635,60 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
634 condition_variable_addr, target); 635 condition_variable_addr, target);
635 636
636 u32 processed = 0; 637 u32 processed = 0;
637 auto& thread_list = Core::System::GetInstance().Scheduler().GetThreadList();
638
639 for (auto& thread : thread_list) {
640 if (thread->condvar_wait_address != condition_variable_addr)
641 continue;
642
643 // Only process up to 'target' threads, unless 'target' is -1, in which case process
644 // them all.
645 if (target != -1 && processed >= target)
646 break;
647
648 // If the mutex is not yet acquired, acquire it.
649 u32 mutex_val = Memory::Read32(thread->mutex_wait_address);
650
651 if (mutex_val == 0) {
652 // We were able to acquire the mutex, resume this thread.
653 Memory::Write32(thread->mutex_wait_address, thread->wait_handle);
654 ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
655 thread->ResumeFromWait();
656
657 auto lock_owner = thread->lock_owner;
658 if (lock_owner)
659 lock_owner->RemoveMutexWaiter(thread);
660
661 thread->lock_owner = nullptr;
662 thread->mutex_wait_address = 0;
663 thread->condvar_wait_address = 0;
664 thread->wait_handle = 0;
665 } else {
666 // Couldn't acquire the mutex, block the thread.
667 Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
668 auto owner = g_handle_table.Get<Thread>(owner_handle);
669 ASSERT(owner);
670 ASSERT(thread->status != THREADSTATUS_RUNNING);
671 thread->status = THREADSTATUS_WAIT_MUTEX;
672 thread->wakeup_callback = nullptr;
673 638
674 // Signal that the mutex now has a waiting thread. 639 auto signal_process_wide_key = [&](size_t core_index) {
675 Memory::Write32(thread->mutex_wait_address, mutex_val | Mutex::MutexHasWaitersFlag); 640 const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
676 641 for (auto& thread : scheduler->GetThreadList()) {
677 owner->AddMutexWaiter(thread); 642 if (thread->condvar_wait_address != condition_variable_addr)
678 643 continue;
679 Core::System::GetInstance().PrepareReschedule(); 644
645 // Only process up to 'target' threads, unless 'target' is -1, in which case process
646 // them all.
647 if (target != -1 && processed >= target)
648 break;
649
650 // If the mutex is not yet acquired, acquire it.
651 u32 mutex_val = Memory::Read32(thread->mutex_wait_address);
652
653 if (mutex_val == 0) {
654 // We were able to acquire the mutex, resume this thread.
655 Memory::Write32(thread->mutex_wait_address, thread->wait_handle);
656 ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
657 thread->ResumeFromWait();
658
659 auto lock_owner = thread->lock_owner;
660 if (lock_owner)
661 lock_owner->RemoveMutexWaiter(thread);
662
663 thread->lock_owner = nullptr;
664 thread->mutex_wait_address = 0;
665 thread->condvar_wait_address = 0;
666 thread->wait_handle = 0;
667 } else {
668 // Couldn't acquire the mutex, block the thread.
669 Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
670 auto owner = g_handle_table.Get<Thread>(owner_handle);
671 ASSERT(owner);
672 ASSERT(thread->status != THREADSTATUS_RUNNING);
673 thread->status = THREADSTATUS_WAIT_MUTEX;
674 thread->wakeup_callback = nullptr;
675
676 // Signal that the mutex now has a waiting thread.
677 Memory::Write32(thread->mutex_wait_address, mutex_val | Mutex::MutexHasWaitersFlag);
678
679 owner->AddMutexWaiter(thread);
680
681 Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
682 }
683
684 ++processed;
680 } 685 }
686 };
681 687
682 ++processed; 688 signal_process_wide_key(0);
683 } 689 signal_process_wide_key(1);
690 signal_process_wide_key(2);
691 signal_process_wide_key(3);
684 692
685 return RESULT_SUCCESS; 693 return RESULT_SUCCESS;
686} 694}
@@ -718,16 +726,31 @@ static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32
718 return RESULT_SUCCESS; 726 return RESULT_SUCCESS;
719} 727}
720 728
721static ResultCode GetThreadCoreMask(Handle handle, u32* mask, u64* unknown) { 729static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask) {
722 NGLOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x{:08X}", handle); 730 NGLOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle);
723 *mask = 0x0; 731
724 *unknown = 0xf; 732 const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
733 if (!thread) {
734 return ERR_INVALID_HANDLE;
735 }
736
737 *core = thread->ideal_core;
738 *mask = thread->affinity_mask;
739
725 return RESULT_SUCCESS; 740 return RESULT_SUCCESS;
726} 741}
727 742
728static ResultCode SetThreadCoreMask(Handle handle, u32 mask, u64 unknown) { 743static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
729 NGLOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x{:08X}, mask=0x{:08X}, unknown=0x{:X}", 744 NGLOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}, mask=0x{:08X}, core=0x{:X}", thread_handle,
730 handle, mask, unknown); 745 mask, core);
746
747 const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
748 if (!thread) {
749 return ERR_INVALID_HANDLE;
750 }
751
752 thread->ChangeCore(core, mask);
753
731 return RESULT_SUCCESS; 754 return RESULT_SUCCESS;
732} 755}
733 756
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index c86ad3e04..40aa88cc1 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.h
@@ -13,14 +13,14 @@
13 13
14namespace Kernel { 14namespace Kernel {
15 15
16#define PARAM(n) Core::CPU().GetReg(n) 16#define PARAM(n) Core::CurrentArmInterface().GetReg(n)
17 17
18/** 18/**
19 * HLE a function return from the current ARM userland process 19 * HLE a function return from the current ARM userland process
20 * @param res Result to return 20 * @param res Result to return
21 */ 21 */
22static inline void FuncReturn(u64 res) { 22static inline void FuncReturn(u64 res) {
23 Core::CPU().SetReg(0, res); 23 Core::CurrentArmInterface().SetReg(0, res);
24} 24}
25 25
26//////////////////////////////////////////////////////////////////////////////////////////////////// 26////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -45,7 +45,7 @@ template <ResultCode func(u32*, u32)>
45void SvcWrap() { 45void SvcWrap() {
46 u32 param_1 = 0; 46 u32 param_1 = 0;
47 u32 retval = func(&param_1, (u32)PARAM(1)).raw; 47 u32 retval = func(&param_1, (u32)PARAM(1)).raw;
48 Core::CPU().SetReg(1, param_1); 48 Core::CurrentArmInterface().SetReg(1, param_1);
49 FuncReturn(retval); 49 FuncReturn(retval);
50} 50}
51 51
@@ -53,7 +53,7 @@ template <ResultCode func(u32*, u64)>
53void SvcWrap() { 53void SvcWrap() {
54 u32 param_1 = 0; 54 u32 param_1 = 0;
55 u32 retval = func(&param_1, PARAM(1)).raw; 55 u32 retval = func(&param_1, PARAM(1)).raw;
56 Core::CPU().SetReg(1, param_1); 56 Core::CurrentArmInterface().SetReg(1, param_1);
57 FuncReturn(retval); 57 FuncReturn(retval);
58} 58}
59 59
@@ -66,7 +66,7 @@ template <ResultCode func(u64*, u64)>
66void SvcWrap() { 66void SvcWrap() {
67 u64 param_1 = 0; 67 u64 param_1 = 0;
68 u32 retval = func(&param_1, PARAM(1)).raw; 68 u32 retval = func(&param_1, PARAM(1)).raw;
69 Core::CPU().SetReg(1, param_1); 69 Core::CurrentArmInterface().SetReg(1, param_1);
70 FuncReturn(retval); 70 FuncReturn(retval);
71} 71}
72 72
@@ -85,8 +85,8 @@ void SvcWrap() {
85 u32 param_1 = 0; 85 u32 param_1 = 0;
86 u64 param_2 = 0; 86 u64 param_2 = 0;
87 ResultCode retval = func((u32)(PARAM(2) & 0xFFFFFFFF), &param_1, &param_2); 87 ResultCode retval = func((u32)(PARAM(2) & 0xFFFFFFFF), &param_1, &param_2);
88 Core::CPU().SetReg(1, param_1); 88 Core::CurrentArmInterface().SetReg(1, param_1);
89 Core::CPU().SetReg(2, param_2); 89 Core::CurrentArmInterface().SetReg(2, param_2);
90 FuncReturn(retval.raw); 90 FuncReturn(retval.raw);
91} 91}
92 92
@@ -120,7 +120,7 @@ template <ResultCode func(u32*, u64, u64, s64)>
120void SvcWrap() { 120void SvcWrap() {
121 u32 param_1 = 0; 121 u32 param_1 = 0;
122 ResultCode retval = func(&param_1, PARAM(1), (u32)(PARAM(2) & 0xFFFFFFFF), (s64)PARAM(3)); 122 ResultCode retval = func(&param_1, PARAM(1), (u32)(PARAM(2) & 0xFFFFFFFF), (s64)PARAM(3));
123 Core::CPU().SetReg(1, param_1); 123 Core::CurrentArmInterface().SetReg(1, param_1);
124 FuncReturn(retval.raw); 124 FuncReturn(retval.raw);
125} 125}
126 126
@@ -133,7 +133,7 @@ template <ResultCode func(u64*, u64, u64, u64)>
133void SvcWrap() { 133void SvcWrap() {
134 u64 param_1 = 0; 134 u64 param_1 = 0;
135 u32 retval = func(&param_1, PARAM(1), PARAM(2), PARAM(3)).raw; 135 u32 retval = func(&param_1, PARAM(1), PARAM(2), PARAM(3)).raw;
136 Core::CPU().SetReg(1, param_1); 136 Core::CurrentArmInterface().SetReg(1, param_1);
137 FuncReturn(retval); 137 FuncReturn(retval);
138} 138}
139 139
@@ -143,7 +143,7 @@ void SvcWrap() {
143 u32 retval = 143 u32 retval =
144 func(&param_1, PARAM(1), PARAM(2), PARAM(3), (u32)PARAM(4), (s32)(PARAM(5) & 0xFFFFFFFF)) 144 func(&param_1, PARAM(1), PARAM(2), PARAM(3), (u32)PARAM(4), (s32)(PARAM(5) & 0xFFFFFFFF))
145 .raw; 145 .raw;
146 Core::CPU().SetReg(1, param_1); 146 Core::CurrentArmInterface().SetReg(1, param_1);
147 FuncReturn(retval); 147 FuncReturn(retval);
148} 148}
149 149
@@ -166,7 +166,7 @@ template <ResultCode func(u32*, u64, u64, u32)>
166void SvcWrap() { 166void SvcWrap() {
167 u32 param_1 = 0; 167 u32 param_1 = 0;
168 u32 retval = func(&param_1, PARAM(1), PARAM(2), (u32)(PARAM(3) & 0xFFFFFFFF)).raw; 168 u32 retval = func(&param_1, PARAM(1), PARAM(2), (u32)(PARAM(3) & 0xFFFFFFFF)).raw;
169 Core::CPU().SetReg(1, param_1); 169 Core::CurrentArmInterface().SetReg(1, param_1);
170 FuncReturn(retval); 170 FuncReturn(retval);
171} 171}
172 172
@@ -175,7 +175,7 @@ void SvcWrap() {
175 u32 param_1 = 0; 175 u32 param_1 = 0;
176 u32 retval = 176 u32 retval =
177 func(&param_1, PARAM(1), (u32)(PARAM(2) & 0xFFFFFFFF), (u32)(PARAM(3) & 0xFFFFFFFF)).raw; 177 func(&param_1, PARAM(1), (u32)(PARAM(2) & 0xFFFFFFFF), (u32)(PARAM(3) & 0xFFFFFFFF)).raw;
178 Core::CPU().SetReg(1, param_1); 178 Core::CurrentArmInterface().SetReg(1, param_1);
179 FuncReturn(retval); 179 FuncReturn(retval);
180} 180}
181 181
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 1bd5d9ebf..46fcdefb8 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -64,7 +64,7 @@ void Thread::Stop() {
64 // Clean up thread from ready queue 64 // Clean up thread from ready queue
65 // This is only needed when the thread is termintated forcefully (SVC TerminateProcess) 65 // This is only needed when the thread is termintated forcefully (SVC TerminateProcess)
66 if (status == THREADSTATUS_READY) { 66 if (status == THREADSTATUS_READY) {
67 Core::System::GetInstance().Scheduler().UnscheduleThread(this, current_priority); 67 scheduler->UnscheduleThread(this, current_priority);
68 } 68 }
69 69
70 status = THREADSTATUS_DEAD; 70 status = THREADSTATUS_DEAD;
@@ -92,7 +92,7 @@ void WaitCurrentThread_Sleep() {
92void ExitCurrentThread() { 92void ExitCurrentThread() {
93 Thread* thread = GetCurrentThread(); 93 Thread* thread = GetCurrentThread();
94 thread->Stop(); 94 thread->Stop();
95 Core::System::GetInstance().Scheduler().RemoveThread(thread); 95 Core::System::GetInstance().CurrentScheduler().RemoveThread(thread);
96} 96}
97 97
98/** 98/**
@@ -154,6 +154,18 @@ void Thread::CancelWakeupTimer() {
154 CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle); 154 CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle);
155} 155}
156 156
157static boost::optional<s32> GetNextProcessorId(u64 mask) {
158 for (s32 index = 0; index < Core::NUM_CPU_CORES; ++index) {
159 if (mask & (1ULL << index)) {
160 if (!Core::System().GetInstance().Scheduler(index)->GetCurrentThread()) {
161 // Core is enabled and not running any threads, use this one
162 return index;
163 }
164 }
165 }
166 return {};
167}
168
157void Thread::ResumeFromWait() { 169void Thread::ResumeFromWait() {
158 ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects"); 170 ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects");
159 171
@@ -188,8 +200,37 @@ void Thread::ResumeFromWait() {
188 wakeup_callback = nullptr; 200 wakeup_callback = nullptr;
189 201
190 status = THREADSTATUS_READY; 202 status = THREADSTATUS_READY;
191 Core::System::GetInstance().Scheduler().ScheduleThread(this, current_priority); 203
192 Core::System::GetInstance().PrepareReschedule(); 204 boost::optional<s32> new_processor_id = GetNextProcessorId(affinity_mask);
205 if (!new_processor_id) {
206 new_processor_id = processor_id;
207 }
208 if (ideal_core != -1 &&
209 Core::System().GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) {
210 new_processor_id = ideal_core;
211 }
212
213 ASSERT(*new_processor_id < 4);
214
215 // Add thread to new core's scheduler
216 auto& next_scheduler = Core::System().GetInstance().Scheduler(*new_processor_id);
217
218 if (*new_processor_id != processor_id) {
219 // Remove thread from previous core's scheduler
220 scheduler->RemoveThread(this);
221 next_scheduler->AddThread(this, current_priority);
222 }
223
224 processor_id = *new_processor_id;
225
226 // If the thread was ready, unschedule from the previous core and schedule on the new core
227 scheduler->UnscheduleThread(this, current_priority);
228 next_scheduler->ScheduleThread(this, current_priority);
229
230 // Change thread's scheduler
231 scheduler = next_scheduler;
232
233 Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
193} 234}
194 235
195/** 236/**
@@ -259,8 +300,6 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
259 300
260 SharedPtr<Thread> thread(new Thread); 301 SharedPtr<Thread> thread(new Thread);
261 302
262 Core::System::GetInstance().Scheduler().AddThread(thread, priority);
263
264 thread->thread_id = NewThreadId(); 303 thread->thread_id = NewThreadId();
265 thread->status = THREADSTATUS_DORMANT; 304 thread->status = THREADSTATUS_DORMANT;
266 thread->entry_point = entry_point; 305 thread->entry_point = entry_point;
@@ -268,6 +307,8 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
268 thread->nominal_priority = thread->current_priority = priority; 307 thread->nominal_priority = thread->current_priority = priority;
269 thread->last_running_ticks = CoreTiming::GetTicks(); 308 thread->last_running_ticks = CoreTiming::GetTicks();
270 thread->processor_id = processor_id; 309 thread->processor_id = processor_id;
310 thread->ideal_core = processor_id;
311 thread->affinity_mask = 1ULL << processor_id;
271 thread->wait_objects.clear(); 312 thread->wait_objects.clear();
272 thread->mutex_wait_address = 0; 313 thread->mutex_wait_address = 0;
273 thread->condvar_wait_address = 0; 314 thread->condvar_wait_address = 0;
@@ -275,6 +316,8 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
275 thread->name = std::move(name); 316 thread->name = std::move(name);
276 thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap(); 317 thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap();
277 thread->owner_process = owner_process; 318 thread->owner_process = owner_process;
319 thread->scheduler = Core::System().GetInstance().Scheduler(processor_id);
320 thread->scheduler->AddThread(thread, priority);
278 321
279 // Find the next available TLS index, and mark it as used 322 // Find the next available TLS index, and mark it as used
280 auto& tls_slots = owner_process->tls_slots; 323 auto& tls_slots = owner_process->tls_slots;
@@ -337,7 +380,7 @@ void Thread::SetPriority(u32 priority) {
337} 380}
338 381
339void Thread::BoostPriority(u32 priority) { 382void Thread::BoostPriority(u32 priority) {
340 Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority); 383 scheduler->SetThreadPriority(this, priority);
341 current_priority = priority; 384 current_priority = priority;
342} 385}
343 386
@@ -406,7 +449,7 @@ void Thread::UpdatePriority() {
406 if (new_priority == current_priority) 449 if (new_priority == current_priority)
407 return; 450 return;
408 451
409 Core::System::GetInstance().Scheduler().SetThreadPriority(this, new_priority); 452 scheduler->SetThreadPriority(this, new_priority);
410 453
411 current_priority = new_priority; 454 current_priority = new_priority;
412 455
@@ -415,13 +458,54 @@ void Thread::UpdatePriority() {
415 lock_owner->UpdatePriority(); 458 lock_owner->UpdatePriority();
416} 459}
417 460
461void Thread::ChangeCore(u32 core, u64 mask) {
462 ideal_core = core;
463 mask = mask;
464
465 if (status != THREADSTATUS_READY) {
466 return;
467 }
468
469 boost::optional<s32> new_processor_id{GetNextProcessorId(mask)};
470
471 if (!new_processor_id) {
472 new_processor_id = processor_id;
473 }
474 if (ideal_core != -1 &&
475 Core::System().GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) {
476 new_processor_id = ideal_core;
477 }
478
479 ASSERT(new_processor_id < 4);
480
481 // Add thread to new core's scheduler
482 auto& next_scheduler = Core::System().GetInstance().Scheduler(*new_processor_id);
483
484 if (*new_processor_id != processor_id) {
485 // Remove thread from previous core's scheduler
486 scheduler->RemoveThread(this);
487 next_scheduler->AddThread(this, current_priority);
488 }
489
490 processor_id = *new_processor_id;
491
492 // If the thread was ready, unschedule from the previous core and schedule on the new core
493 scheduler->UnscheduleThread(this, current_priority);
494 next_scheduler->ScheduleThread(this, current_priority);
495
496 // Change thread's scheduler
497 scheduler = next_scheduler;
498
499 Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
500}
501
418//////////////////////////////////////////////////////////////////////////////////////////////////// 502////////////////////////////////////////////////////////////////////////////////////////////////////
419 503
420/** 504/**
421 * Gets the current thread 505 * Gets the current thread
422 */ 506 */
423Thread* GetCurrentThread() { 507Thread* GetCurrentThread() {
424 return Core::System::GetInstance().Scheduler().GetCurrentThread(); 508 return Core::System::GetInstance().CurrentScheduler().GetCurrentThread();
425} 509}
426 510
427void ThreadingInit() { 511void ThreadingInit() {
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index e0a3c0934..1d2da6d50 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <memory>
7#include <string> 8#include <string>
8#include <unordered_map> 9#include <unordered_map>
9#include <vector> 10#include <vector>
@@ -56,6 +57,7 @@ enum class ThreadWakeupReason {
56namespace Kernel { 57namespace Kernel {
57 58
58class Process; 59class Process;
60class Scheduler;
59 61
60class Thread final : public WaitObject { 62class Thread final : public WaitObject {
61public: 63public:
@@ -118,6 +120,9 @@ public:
118 /// Recalculates the current priority taking into account priority inheritance. 120 /// Recalculates the current priority taking into account priority inheritance.
119 void UpdatePriority(); 121 void UpdatePriority();
120 122
123 /// Changes the core that the thread is running or scheduled to run on.
124 void ChangeCore(u32 core, u64 mask);
125
121 /** 126 /**
122 * Gets the thread's thread ID 127 * Gets the thread's thread ID
123 * @return The thread's ID 128 * @return The thread's ID
@@ -240,6 +245,11 @@ public:
240 // available. In case of a timeout, the object will be nullptr. 245 // available. In case of a timeout, the object will be nullptr.
241 std::function<WakeupCallback> wakeup_callback; 246 std::function<WakeupCallback> wakeup_callback;
242 247
248 std::shared_ptr<Scheduler> scheduler;
249
250 u32 ideal_core{0xFFFFFFFF};
251 u64 affinity_mask{0x1};
252
243private: 253private:
244 Thread(); 254 Thread();
245 ~Thread() override; 255 ~Thread() override;
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index 2f0044c11..676e5b282 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -104,8 +104,15 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
104 VirtualMemoryArea& final_vma = vma_handle->second; 104 VirtualMemoryArea& final_vma = vma_handle->second;
105 ASSERT(final_vma.size == size); 105 ASSERT(final_vma.size == size);
106 106
107 Core::CPU().MapBackingMemory(target, size, block->data() + offset, 107 auto& system = Core::System::GetInstance();
108 VMAPermission::ReadWriteExecute); 108 system.ArmInterface(0).MapBackingMemory(target, size, block->data() + offset,
109 VMAPermission::ReadWriteExecute);
110 system.ArmInterface(1).MapBackingMemory(target, size, block->data() + offset,
111 VMAPermission::ReadWriteExecute);
112 system.ArmInterface(2).MapBackingMemory(target, size, block->data() + offset,
113 VMAPermission::ReadWriteExecute);
114 system.ArmInterface(3).MapBackingMemory(target, size, block->data() + offset,
115 VMAPermission::ReadWriteExecute);
109 116
110 final_vma.type = VMAType::AllocatedMemoryBlock; 117 final_vma.type = VMAType::AllocatedMemoryBlock;
111 final_vma.permissions = VMAPermission::ReadWrite; 118 final_vma.permissions = VMAPermission::ReadWrite;
@@ -126,7 +133,11 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me
126 VirtualMemoryArea& final_vma = vma_handle->second; 133 VirtualMemoryArea& final_vma = vma_handle->second;
127 ASSERT(final_vma.size == size); 134 ASSERT(final_vma.size == size);
128 135
129 Core::CPU().MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); 136 auto& system = Core::System::GetInstance();
137 system.ArmInterface(0).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
138 system.ArmInterface(1).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
139 system.ArmInterface(2).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
140 system.ArmInterface(3).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute);
130 141
131 final_vma.type = VMAType::BackingMemory; 142 final_vma.type = VMAType::BackingMemory;
132 final_vma.permissions = VMAPermission::ReadWrite; 143 final_vma.permissions = VMAPermission::ReadWrite;
@@ -184,7 +195,11 @@ ResultCode VMManager::UnmapRange(VAddr target, u64 size) {
184 195
185 ASSERT(FindVMA(target)->second.size >= size); 196 ASSERT(FindVMA(target)->second.size >= size);
186 197
187 Core::CPU().UnmapMemory(target, size); 198 auto& system = Core::System::GetInstance();
199 system.ArmInterface(0).UnmapMemory(target, size);
200 system.ArmInterface(1).UnmapMemory(target, size);
201 system.ArmInterface(2).UnmapMemory(target, size);
202 system.ArmInterface(3).UnmapMemory(target, size);
188 203
189 return RESULT_SUCCESS; 204 return RESULT_SUCCESS;
190} 205}
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index db8211463..3b81acd63 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -28,8 +28,13 @@ static PageTable* current_page_table = nullptr;
28 28
29void SetCurrentPageTable(PageTable* page_table) { 29void SetCurrentPageTable(PageTable* page_table) {
30 current_page_table = page_table; 30 current_page_table = page_table;
31 if (Core::System::GetInstance().IsPoweredOn()) { 31
32 Core::CPU().PageTableChanged(); 32 auto& system = Core::System::GetInstance();
33 if (system.IsPoweredOn()) {
34 system.ArmInterface(0).PageTableChanged();
35 system.ArmInterface(1).PageTableChanged();
36 system.ArmInterface(2).PageTableChanged();
37 system.ArmInterface(3).PageTableChanged();
33 } 38 }
34} 39}
35 40
diff --git a/src/core/settings.h b/src/core/settings.h
index cfec63c21..a7f1e5fa0 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -121,6 +121,7 @@ struct Values {
121 121
122 // Core 122 // Core
123 bool use_cpu_jit; 123 bool use_cpu_jit;
124 bool use_multi_core;
124 125
125 // Data Storage 126 // Data Storage
126 bool use_virtual_sd; 127 bool use_virtual_sd;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 02c52bb55..a60aa1143 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -155,6 +155,8 @@ TelemetrySession::TelemetrySession() {
155 155
156 // Log user configuration information 156 // Log user configuration information
157 AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit); 157 AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit);
158 AddField(Telemetry::FieldType::UserConfig, "Core_UseMultiCore",
159 Settings::values.use_multi_core);
158 AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor", 160 AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor",
159 Settings::values.resolution_factor); 161 Settings::values.resolution_factor);
160 AddField(Telemetry::FieldType::UserConfig, "Renderer_ToggleFramelimit", 162 AddField(Telemetry::FieldType::UserConfig, "Renderer_ToggleFramelimit",
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 8843f2078..8316db708 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -78,6 +78,7 @@ void Config::ReadValues() {
78 78
79 qt_config->beginGroup("Core"); 79 qt_config->beginGroup("Core");
80 Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool(); 80 Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool();
81 Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool();
81 qt_config->endGroup(); 82 qt_config->endGroup();
82 83
83 qt_config->beginGroup("Renderer"); 84 qt_config->beginGroup("Renderer");
@@ -177,6 +178,7 @@ void Config::SaveValues() {
177 178
178 qt_config->beginGroup("Core"); 179 qt_config->beginGroup("Core");
179 qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit); 180 qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit);
181 qt_config->setValue("use_multi_core", Settings::values.use_multi_core);
180 qt_config->endGroup(); 182 qt_config->endGroup();
181 183
182 qt_config->beginGroup("Renderer"); 184 qt_config->beginGroup("Renderer");
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 2d73fc5aa..baa558667 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -20,6 +20,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
20 this->setConfiguration(); 20 this->setConfiguration();
21 21
22 ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); 22 ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
23 ui->use_multi_core->setEnabled(!Core::System::GetInstance().IsPoweredOn());
23 ui->use_docked_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn()); 24 ui->use_docked_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn());
24} 25}
25 26
@@ -30,6 +31,7 @@ void ConfigureGeneral::setConfiguration() {
30 ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); 31 ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
31 ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); 32 ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
32 ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit); 33 ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
34 ui->use_multi_core->setChecked(Settings::values.use_multi_core);
33 ui->use_docked_mode->setChecked(Settings::values.use_docked_mode); 35 ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);
34} 36}
35 37
@@ -40,6 +42,7 @@ void ConfigureGeneral::applyConfiguration() {
40 ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); 42 ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
41 43
42 Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked(); 44 Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked();
45 Settings::values.use_multi_core = ui->use_multi_core->isChecked();
43 Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); 46 Settings::values.use_docked_mode = ui->use_docked_mode->isChecked();
44 Settings::Apply(); 47 Settings::Apply();
45} 48}
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index 1775c4d40..233adbe27 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -58,6 +58,13 @@
58 </property> 58 </property>
59 </widget> 59 </widget>
60 </item> 60 </item>
61 <item>
62 <widget class="QCheckBox" name="use_multi_core">
63 <property name="text">
64 <string>Enable multi-core</string>
65 </property>
66 </widget>
67 </item>
61 </layout> 68 </layout>
62 </item> 69 </item>
63 </layout> 70 </layout>
diff --git a/src/yuzu/debugger/registers.cpp b/src/yuzu/debugger/registers.cpp
index 06e2d1647..178cc65a7 100644
--- a/src/yuzu/debugger/registers.cpp
+++ b/src/yuzu/debugger/registers.cpp
@@ -63,7 +63,7 @@ void RegistersWidget::OnDebugModeEntered() {
63 63
64 for (int i = 0; i < core_registers->childCount(); ++i) 64 for (int i = 0; i < core_registers->childCount(); ++i)
65 core_registers->child(i)->setText( 65 core_registers->child(i)->setText(
66 1, QString("0x%1").arg(Core::CPU().GetReg(i), 8, 16, QLatin1Char('0'))); 66 1, QString("0x%1").arg(Core::CurrentArmInterface().GetReg(i), 8, 16, QLatin1Char('0')));
67 67
68 UpdateCPSRValues(); 68 UpdateCPSRValues();
69} 69}
@@ -122,7 +122,7 @@ void RegistersWidget::CreateCPSRChildren() {
122} 122}
123 123
124void RegistersWidget::UpdateCPSRValues() { 124void RegistersWidget::UpdateCPSRValues() {
125 const u32 cpsr_val = Core::CPU().GetCPSR(); 125 const u32 cpsr_val = Core::CurrentArmInterface().GetCPSR();
126 126
127 cpsr->setText(1, QString("0x%1").arg(cpsr_val, 8, 16, QLatin1Char('0'))); 127 cpsr->setText(1, QString("0x%1").arg(cpsr_val, 8, 16, QLatin1Char('0')));
128 cpsr->child(0)->setText( 128 cpsr->child(0)->setText(
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index acc4c2e0b..8b074db5a 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -51,13 +51,21 @@ std::size_t WaitTreeItem::Row() const {
51} 51}
52 52
53std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() { 53std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() {
54 const auto& threads = Core::System::GetInstance().Scheduler().GetThreadList();
55 std::vector<std::unique_ptr<WaitTreeThread>> item_list; 54 std::vector<std::unique_ptr<WaitTreeThread>> item_list;
56 item_list.reserve(threads.size()); 55 std::size_t row = 0;
57 for (std::size_t i = 0; i < threads.size(); ++i) { 56 auto add_threads = [&](const std::vector<Kernel::SharedPtr<Kernel::Thread>>& threads) {
58 item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i])); 57 for (std::size_t i = 0; i < threads.size(); ++i) {
59 item_list.back()->row = i; 58 item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i]));
60 } 59 item_list.back()->row = row;
60 ++row;
61 }
62 };
63
64 add_threads(Core::System::GetInstance().Scheduler(0)->GetThreadList());
65 add_threads(Core::System::GetInstance().Scheduler(1)->GetThreadList());
66 add_threads(Core::System::GetInstance().Scheduler(2)->GetThreadList());
67 add_threads(Core::System::GetInstance().Scheduler(3)->GetThreadList());
68
61 return item_list; 69 return item_list;
62} 70}
63 71
@@ -240,6 +248,9 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
240 } 248 }
241 249
242 list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor))); 250 list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor)));
251 list.push_back(std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.ideal_core)));
252 list.push_back(
253 std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.affinity_mask)));
243 list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId()))); 254 list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId())));
244 list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)") 255 list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)")
245 .arg(thread.current_priority) 256 .arg(thread.current_priority)
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 675f9cafa..ee6e4d658 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -91,6 +91,7 @@ void Config::ReadValues() {
91 91
92 // Core 92 // Core
93 Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); 93 Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
94 Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false);
94 95
95 // Renderer 96 // Renderer
96 Settings::values.resolution_factor = 97 Settings::values.resolution_factor =
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 02254403d..1c438c3f5 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -80,6 +80,10 @@ touch_device=
80# 0: Interpreter (slow), 1 (default): JIT (fast) 80# 0: Interpreter (slow), 1 (default): JIT (fast)
81use_cpu_jit = 81use_cpu_jit =
82 82
83# Whether to use multi-core for CPU emulation
84# 0 (default): Disabled, 1: Enabled
85use_multi_core=
86
83[Renderer] 87[Renderer]
84# Whether to use software or hardware rendering. 88# Whether to use software or hardware rendering.
85# 0: Software, 1 (default): Hardware 89# 0: Software, 1 (default): Hardware