summaryrefslogtreecommitdiff
path: root/src/core/hle/kernel
diff options
context:
space:
mode:
authorGravatar Subv2016-12-03 22:38:14 -0500
committerGravatar Subv2016-12-03 22:38:14 -0500
commit8634b8cb83755b6c6554faa11c0e488d2ad21f90 (patch)
tree93c2e91659ccd2925210dcffb559213edbd2a64a /src/core/hle/kernel
parentMerge pull request #2251 from JayFoxRox/remove-version (diff)
downloadyuzu-8634b8cb83755b6c6554faa11c0e488d2ad21f90.tar.gz
yuzu-8634b8cb83755b6c6554faa11c0e488d2ad21f90.tar.xz
yuzu-8634b8cb83755b6c6554faa11c0e488d2ad21f90.zip
Threading: Reworked the way our scheduler works.
Threads will now be awakened when the objects they're waiting on are signaled, instead of repeating the WaitSynchronization call every now and then. The scheduler is now called once after every SVC call, and once after a thread is awakened from sleep by its timeout callback. This new implementation is based off reverse-engineering of the real kernel. See https://gist.github.com/Subv/02f29bd9f1e5deb7aceea1e8f019c8f4 for a more detailed description of how the real kernel handles rescheduling.
Diffstat (limited to 'src/core/hle/kernel')
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp2
-rw-r--r--src/core/hle/kernel/kernel.cpp59
-rw-r--r--src/core/hle/kernel/kernel.h3
-rw-r--r--src/core/hle/kernel/thread.cpp97
-rw-r--r--src/core/hle/kernel/thread.h22
-rw-r--r--src/core/hle/kernel/timer.cpp4
6 files changed, 76 insertions, 111 deletions
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
index 37eec4c84..b5a0cc3a3 100644
--- a/src/core/hle/kernel/address_arbiter.cpp
+++ b/src/core/hle/kernel/address_arbiter.cpp
@@ -79,8 +79,6 @@ ResultCode AddressArbiter::ArbitrateAddress(ArbitrationType type, VAddr address,
79 ErrorSummary::WrongArgument, ErrorLevel::Usage); 79 ErrorSummary::WrongArgument, ErrorLevel::Usage);
80 } 80 }
81 81
82 HLE::Reschedule(__func__);
83
84 // The calls that use a timeout seem to always return a Timeout error even if they did not put 82 // The calls that use a timeout seem to always return a Timeout error even if they did not put
85 // the thread to sleep 83 // the thread to sleep
86 if (type == ArbitrationType::WaitIfLessThanWithTimeout || 84 if (type == ArbitrationType::WaitIfLessThanWithTimeout ||
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 0c8752670..be7a5a6d8 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -31,13 +31,62 @@ void WaitObject::RemoveWaitingThread(Thread* thread) {
31 waiting_threads.erase(itr); 31 waiting_threads.erase(itr);
32} 32}
33 33
34void WaitObject::WakeupAllWaitingThreads() { 34SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
35 for (auto thread : waiting_threads) 35 // Remove the threads that are ready or already running from our waitlist
36 thread->ResumeFromWait(); 36 waiting_threads.erase(std::remove_if(waiting_threads.begin(), waiting_threads.end(), [](SharedPtr<Thread> thread) -> bool {
37 return thread->status == THREADSTATUS_RUNNING || thread->status == THREADSTATUS_READY;
38 }), waiting_threads.end());
39
40 if (waiting_threads.empty())
41 return nullptr;
37 42
38 waiting_threads.clear(); 43 auto candidate_threads = waiting_threads;
39 44
40 HLE::Reschedule(__func__); 45 // Eliminate all threads that are waiting on more than one object, and not all of them are ready
46 candidate_threads.erase(std::remove_if(candidate_threads.begin(), candidate_threads.end(), [](SharedPtr<Thread> thread) -> bool {
47 for (auto object : thread->wait_objects)
48 if (object->ShouldWait())
49 return true;
50 return false;
51 }), candidate_threads.end());
52
53 // Return the thread with the lowest priority value (The one with the highest priority)
54 auto thread_itr = std::min_element(candidate_threads.begin(), candidate_threads.end(), [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
55 return lhs->current_priority < rhs->current_priority;
56 });
57
58 if (thread_itr == candidate_threads.end())
59 return nullptr;
60
61 return *thread_itr;
62}
63
64void WaitObject::WakeupAllWaitingThreads() {
65 // Wake up all threads that can be awoken, in priority order
66 while (auto thread = GetHighestPriorityReadyThread()) {
67 if (thread->wait_objects.empty()) {
68 Acquire();
69 // Set the output index of the WaitSynchronizationN call to the index of this object.
70 if (thread->wait_set_output) {
71 thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(this));
72 thread->wait_set_output = false;
73 }
74 } else {
75 for (auto object : thread->wait_objects) {
76 object->Acquire();
77 // Remove the thread from the object's waitlist
78 object->RemoveWaitingThread(thread.get());
79 }
80 // Note: This case doesn't update the output index of WaitSynchronizationN.
81 // Clear the thread's waitlist
82 thread->wait_objects.clear();
83 }
84
85 // Set the result of the call to WaitSynchronization to RESULT_SUCCESS
86 thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
87 thread->ResumeFromWait();
88 // Note: Removing the thread from the object's waitlist will be done by GetHighestPriorityReadyThread
89 }
41} 90}
42 91
43const std::vector<SharedPtr<Thread>>& WaitObject::GetWaitingThreads() const { 92const std::vector<SharedPtr<Thread>>& WaitObject::GetWaitingThreads() const {
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 231cf7b75..eb5a3bf7e 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -155,6 +155,9 @@ public:
155 /// Wake up all threads waiting on this object 155 /// Wake up all threads waiting on this object
156 void WakeupAllWaitingThreads(); 156 void WakeupAllWaitingThreads();
157 157
158 /// Obtains the highest priority thread that is ready to run from this object's waiting list.
159 SharedPtr<Thread> GetHighestPriorityReadyThread();
160
158 /// Get a const reference to the waiting threads list for debug use 161 /// Get a const reference to the waiting threads list for debug use
159 const std::vector<SharedPtr<Thread>>& GetWaitingThreads() const; 162 const std::vector<SharedPtr<Thread>>& GetWaitingThreads() const;
160 163
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 84d6d24c6..49ed9d899 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -120,8 +120,6 @@ void Thread::Stop() {
120 u32 tls_slot = 120 u32 tls_slot =
121 ((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE; 121 ((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE;
122 Kernel::g_current_process->tls_slots[tls_page].reset(tls_slot); 122 Kernel::g_current_process->tls_slots[tls_page].reset(tls_slot);
123
124 HLE::Reschedule(__func__);
125} 123}
126 124
127Thread* ArbitrateHighestPriorityThread(u32 address) { 125Thread* ArbitrateHighestPriorityThread(u32 address) {
@@ -181,50 +179,6 @@ static void PriorityBoostStarvedThreads() {
181} 179}
182 180
183/** 181/**
184 * Gets the registers for timeout parameter of the next WaitSynchronization call.
185 * @param thread a pointer to the thread that is ready to call WaitSynchronization
186 * @returns a tuple of two register pointers to low and high part of the timeout parameter
187 */
188static std::tuple<u32*, u32*> GetWaitSynchTimeoutParameterRegister(Thread* thread) {
189 bool thumb_mode = (thread->context.cpsr & TBIT) != 0;
190 u16 thumb_inst = Memory::Read16(thread->context.pc & 0xFFFFFFFE);
191 u32 inst = Memory::Read32(thread->context.pc & 0xFFFFFFFC) & 0x0FFFFFFF;
192
193 if ((thumb_mode && thumb_inst == 0xDF24) || (!thumb_mode && inst == 0x0F000024)) {
194 // svc #0x24 (WaitSynchronization1)
195 return std::make_tuple(&thread->context.cpu_registers[2],
196 &thread->context.cpu_registers[3]);
197 } else if ((thumb_mode && thumb_inst == 0xDF25) || (!thumb_mode && inst == 0x0F000025)) {
198 // svc #0x25 (WaitSynchronizationN)
199 return std::make_tuple(&thread->context.cpu_registers[0],
200 &thread->context.cpu_registers[4]);
201 }
202
203 UNREACHABLE();
204}
205
206/**
207 * Updates the WaitSynchronization timeout parameter according to the difference
208 * between ticks of the last WaitSynchronization call and the incoming one.
209 * @param timeout_low a pointer to the register for the low part of the timeout parameter
210 * @param timeout_high a pointer to the register for the high part of the timeout parameter
211 * @param last_tick tick of the last WaitSynchronization call
212 */
213static void UpdateTimeoutParameter(u32* timeout_low, u32* timeout_high, u64 last_tick) {
214 s64 timeout = ((s64)*timeout_high << 32) | *timeout_low;
215
216 if (timeout != -1) {
217 timeout -= cyclesToUs(CoreTiming::GetTicks() - last_tick) * 1000; // in nanoseconds
218
219 if (timeout < 0)
220 timeout = 0;
221
222 *timeout_low = timeout & 0xFFFFFFFF;
223 *timeout_high = timeout >> 32;
224 }
225}
226
227/**
228 * Switches the CPU's active thread context to that of the specified thread 182 * Switches the CPU's active thread context to that of the specified thread
229 * @param new_thread The thread to switch to 183 * @param new_thread The thread to switch to
230 */ 184 */
@@ -254,32 +208,6 @@ static void SwitchContext(Thread* new_thread) {
254 208
255 current_thread = new_thread; 209 current_thread = new_thread;
256 210
257 // If the thread was waited by a svcWaitSynch call, step back PC by one instruction to rerun
258 // the SVC when the thread wakes up. This is necessary to ensure that the thread can acquire
259 // the requested wait object(s) before continuing.
260 if (new_thread->waitsynch_waited) {
261 // CPSR flag indicates CPU mode
262 bool thumb_mode = (new_thread->context.cpsr & TBIT) != 0;
263
264 // SVC instruction is 2 bytes for THUMB, 4 bytes for ARM
265 new_thread->context.pc -= thumb_mode ? 2 : 4;
266
267 // Get the register for timeout parameter
268 u32 *timeout_low, *timeout_high;
269 std::tie(timeout_low, timeout_high) = GetWaitSynchTimeoutParameterRegister(new_thread);
270
271 // Update the timeout parameter
272 UpdateTimeoutParameter(timeout_low, timeout_high, new_thread->last_running_ticks);
273 }
274
275 // Clean up the thread's wait_objects, they'll be restored if needed during
276 // the svcWaitSynchronization call
277 for (size_t i = 0; i < new_thread->wait_objects.size(); ++i) {
278 SharedPtr<WaitObject> object = new_thread->wait_objects[i];
279 object->RemoveWaitingThread(new_thread);
280 }
281 new_thread->wait_objects.clear();
282
283 ready_queue.remove(new_thread->current_priority, new_thread); 211 ready_queue.remove(new_thread->current_priority, new_thread);
284 new_thread->status = THREADSTATUS_RUNNING; 212 new_thread->status = THREADSTATUS_RUNNING;
285 213
@@ -319,17 +247,13 @@ static Thread* PopNextReadyThread() {
319void WaitCurrentThread_Sleep() { 247void WaitCurrentThread_Sleep() {
320 Thread* thread = GetCurrentThread(); 248 Thread* thread = GetCurrentThread();
321 thread->status = THREADSTATUS_WAIT_SLEEP; 249 thread->status = THREADSTATUS_WAIT_SLEEP;
322
323 HLE::Reschedule(__func__);
324} 250}
325 251
326void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects, 252void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects,
327 bool wait_set_output, bool wait_all) { 253 bool wait_set_output) {
328 Thread* thread = GetCurrentThread(); 254 Thread* thread = GetCurrentThread();
329 thread->wait_set_output = wait_set_output; 255 thread->wait_set_output = wait_set_output;
330 thread->wait_all = wait_all;
331 thread->wait_objects = std::move(wait_objects); 256 thread->wait_objects = std::move(wait_objects);
332 thread->waitsynch_waited = true;
333 thread->status = THREADSTATUS_WAIT_SYNCH; 257 thread->status = THREADSTATUS_WAIT_SYNCH;
334} 258}
335 259
@@ -351,15 +275,11 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
351 return; 275 return;
352 } 276 }
353 277
354 thread->waitsynch_waited = false;
355
356 if (thread->status == THREADSTATUS_WAIT_SYNCH || thread->status == THREADSTATUS_WAIT_ARB) { 278 if (thread->status == THREADSTATUS_WAIT_SYNCH || thread->status == THREADSTATUS_WAIT_ARB) {
279 thread->wait_set_output = false;
357 thread->SetWaitSynchronizationResult(ResultCode(ErrorDescription::Timeout, ErrorModule::OS, 280 thread->SetWaitSynchronizationResult(ResultCode(ErrorDescription::Timeout, ErrorModule::OS,
358 ErrorSummary::StatusChanged, 281 ErrorSummary::StatusChanged,
359 ErrorLevel::Info)); 282 ErrorLevel::Info));
360
361 if (thread->wait_set_output)
362 thread->SetWaitSynchronizationOutput(-1);
363 } 283 }
364 284
365 thread->ResumeFromWait(); 285 thread->ResumeFromWait();
@@ -399,6 +319,7 @@ void Thread::ResumeFromWait() {
399 319
400 ready_queue.push_back(current_priority, this); 320 ready_queue.push_back(current_priority, this);
401 status = THREADSTATUS_READY; 321 status = THREADSTATUS_READY;
322 HLE::Reschedule(__func__);
402} 323}
403 324
404/** 325/**
@@ -494,13 +415,11 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
494 thread->last_running_ticks = CoreTiming::GetTicks(); 415 thread->last_running_ticks = CoreTiming::GetTicks();
495 thread->processor_id = processor_id; 416 thread->processor_id = processor_id;
496 thread->wait_set_output = false; 417 thread->wait_set_output = false;
497 thread->wait_all = false;
498 thread->wait_objects.clear(); 418 thread->wait_objects.clear();
499 thread->wait_address = 0; 419 thread->wait_address = 0;
500 thread->name = std::move(name); 420 thread->name = std::move(name);
501 thread->callback_handle = wakeup_callback_handle_table.Create(thread).MoveFrom(); 421 thread->callback_handle = wakeup_callback_handle_table.Create(thread).MoveFrom();
502 thread->owner_process = g_current_process; 422 thread->owner_process = g_current_process;
503 thread->waitsynch_waited = false;
504 423
505 // Find the next available TLS index, and mark it as used 424 // Find the next available TLS index, and mark it as used
506 auto& tls_slots = Kernel::g_current_process->tls_slots; 425 auto& tls_slots = Kernel::g_current_process->tls_slots;
@@ -555,8 +474,6 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
555 ready_queue.push_back(thread->current_priority, thread.get()); 474 ready_queue.push_back(thread->current_priority, thread.get());
556 thread->status = THREADSTATUS_READY; 475 thread->status = THREADSTATUS_READY;
557 476
558 HLE::Reschedule(__func__);
559
560 return MakeResult<SharedPtr<Thread>>(std::move(thread)); 477 return MakeResult<SharedPtr<Thread>>(std::move(thread));
561} 478}
562 479
@@ -619,14 +536,6 @@ void Reschedule() {
619 536
620 HLE::DoneRescheduling(); 537 HLE::DoneRescheduling();
621 538
622 // Don't bother switching to the same thread.
623 // But if the thread was waiting on objects, we still need to switch it
624 // to perform PC modification, change state to RUNNING, etc.
625 // This occurs in the case when an object the thread is waiting on immediately wakes up
626 // the current thread before Reschedule() is called.
627 if (next == cur && (next == nullptr || next->waitsynch_waited == false))
628 return;
629
630 if (cur && next) { 539 if (cur && next) {
631 LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId()); 540 LOG_TRACE(Kernel, "context switch %u -> %u", cur->GetObjectId(), next->GetObjectId());
632 } else if (cur) { 541 } else if (cur) {
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index e0ffcea8a..63b97b74f 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -5,6 +5,7 @@
5#pragma once 5#pragma once
6 6
7#include <string> 7#include <string>
8#include <unordered_map>
8#include <vector> 9#include <vector>
9#include <boost/container/flat_set.hpp> 10#include <boost/container/flat_set.hpp>
10#include "common/common_types.h" 11#include "common/common_types.h"
@@ -125,6 +126,16 @@ public:
125 void SetWaitSynchronizationOutput(s32 output); 126 void SetWaitSynchronizationOutput(s32 output);
126 127
127 /** 128 /**
129 * Retrieves the index that this particular object occupies in the list of objects
130 * that the thread passed to WaitSynchronizationN.
131 * It is used to set the output value of WaitSynchronizationN when the thread is awakened.
132 * @param object Object to query the index of.
133 */
134 s32 GetWaitObjectIndex(WaitObject* object) {
135 return wait_objects_index[object->GetObjectId()];
136 }
137
138 /**
128 * Stops a thread, invalidating it from further use 139 * Stops a thread, invalidating it from further use
129 */ 140 */
130 void Stop(); 141 void Stop();
@@ -154,16 +165,16 @@ public:
154 165
155 VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread 166 VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread
156 167
157 bool waitsynch_waited; ///< Set to true if the last svcWaitSynch call caused the thread to wait
158
159 /// Mutexes currently held by this thread, which will be released when it exits. 168 /// Mutexes currently held by this thread, which will be released when it exits.
160 boost::container::flat_set<SharedPtr<Mutex>> held_mutexes; 169 boost::container::flat_set<SharedPtr<Mutex>> held_mutexes;
161 170
162 SharedPtr<Process> owner_process; ///< Process that owns this thread 171 SharedPtr<Process> owner_process; ///< Process that owns this thread
163 std::vector<SharedPtr<WaitObject>> wait_objects; ///< Objects that the thread is waiting on 172 std::vector<SharedPtr<WaitObject>> wait_objects; ///< Objects that the thread is waiting on
173 std::unordered_map<int, s32> wait_objects_index; ///< Mapping of Object ids to their position in the last waitlist that this object waited on.
174
164 VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address 175 VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address
165 bool wait_all; ///< True if the thread is waiting on all objects before resuming 176
166 bool wait_set_output; ///< True if the output parameter should be set on thread wakeup 177 bool wait_set_output; ///< True if the WaitSynchronizationN output parameter should be set on thread wakeup
167 178
168 std::string name; 179 std::string name;
169 180
@@ -215,10 +226,9 @@ void WaitCurrentThread_Sleep();
215 * @param wait_objects Kernel objects that we are waiting on 226 * @param wait_objects Kernel objects that we are waiting on
216 * @param wait_set_output If true, set the output parameter on thread wakeup (for 227 * @param wait_set_output If true, set the output parameter on thread wakeup (for
217 * WaitSynchronizationN only) 228 * WaitSynchronizationN only)
218 * @param wait_all If true, wait on all objects before resuming (for WaitSynchronizationN only)
219 */ 229 */
220void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects, 230void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects,
221 bool wait_set_output, bool wait_all); 231 bool wait_set_output);
222 232
223/** 233/**
224 * Waits the current thread from an ArbitrateAddress call 234 * Waits the current thread from an ArbitrateAddress call
diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp
index eac181f4e..b50cf520d 100644
--- a/src/core/hle/kernel/timer.cpp
+++ b/src/core/hle/kernel/timer.cpp
@@ -60,14 +60,10 @@ void Timer::Set(s64 initial, s64 interval) {
60 u64 initial_microseconds = initial / 1000; 60 u64 initial_microseconds = initial / 1000;
61 CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type, 61 CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type,
62 callback_handle); 62 callback_handle);
63
64 HLE::Reschedule(__func__);
65} 63}
66 64
67void Timer::Cancel() { 65void Timer::Cancel() {
68 CoreTiming::UnscheduleEvent(timer_callback_event_type, callback_handle); 66 CoreTiming::UnscheduleEvent(timer_callback_event_type, callback_handle);
69
70 HLE::Reschedule(__func__);
71} 67}
72 68
73void Timer::Clear() { 69void Timer::Clear() {