summaryrefslogtreecommitdiff
path: root/src/core/hle
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
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')
-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
-rw-r--r--src/core/hle/svc.cpp181
7 files changed, 179 insertions, 189 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() {
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp
index c6b80dc50..061692af8 100644
--- a/src/core/hle/svc.cpp
+++ b/src/core/hle/svc.cpp
@@ -249,27 +249,30 @@ static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) {
249 auto object = Kernel::g_handle_table.GetWaitObject(handle); 249 auto object = Kernel::g_handle_table.GetWaitObject(handle);
250 Kernel::Thread* thread = Kernel::GetCurrentThread(); 250 Kernel::Thread* thread = Kernel::GetCurrentThread();
251 251
252 thread->waitsynch_waited = false;
253
254 if (object == nullptr) 252 if (object == nullptr)
255 return ERR_INVALID_HANDLE; 253 return ERR_INVALID_HANDLE;
256 254
257 LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s:%s), nanoseconds=%lld", handle, 255 LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s:%s), nanoseconds=%lld", handle,
258 object->GetTypeName().c_str(), object->GetName().c_str(), nano_seconds); 256 object->GetTypeName().c_str(), object->GetName().c_str(), nano_seconds);
259 257
260 HLE::Reschedule(__func__);
261
262 // Check for next thread to schedule
263 if (object->ShouldWait()) { 258 if (object->ShouldWait()) {
264 259
260 if (nano_seconds == 0)
261 return ResultCode(ErrorDescription::Timeout, ErrorModule::OS,
262 ErrorSummary::StatusChanged,
263 ErrorLevel::Info);
264
265 object->AddWaitingThread(thread); 265 object->AddWaitingThread(thread);
266 Kernel::WaitCurrentThread_WaitSynchronization({object}, false, false); 266 thread->status = THREADSTATUS_WAIT_SYNCH;
267 267
268 // Create an event to wake the thread up after the specified nanosecond delay has passed 268 // Create an event to wake the thread up after the specified nanosecond delay has passed
269 thread->WakeAfterDelay(nano_seconds); 269 thread->WakeAfterDelay(nano_seconds);
270 270
271 // NOTE: output of this SVC will be set later depending on how the thread resumes 271 // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a signal in one of its wait objects.
272 return HLE::RESULT_INVALID; 272 // Otherwise we retain the default value of timeout.
273 return ResultCode(ErrorDescription::Timeout, ErrorModule::OS,
274 ErrorSummary::StatusChanged,
275 ErrorLevel::Info);
273 } 276 }
274 277
275 object->Acquire(); 278 object->Acquire();
@@ -283,8 +286,6 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou
283 bool wait_thread = !wait_all; 286 bool wait_thread = !wait_all;
284 int handle_index = 0; 287 int handle_index = 0;
285 Kernel::Thread* thread = Kernel::GetCurrentThread(); 288 Kernel::Thread* thread = Kernel::GetCurrentThread();
286 bool was_waiting = thread->waitsynch_waited;
287 thread->waitsynch_waited = false;
288 289
289 // Check if 'handles' is invalid 290 // Check if 'handles' is invalid
290 if (handles == nullptr) 291 if (handles == nullptr)
@@ -300,90 +301,113 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou
300 return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS, 301 return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS,
301 ErrorSummary::InvalidArgument, ErrorLevel::Usage); 302 ErrorSummary::InvalidArgument, ErrorLevel::Usage);
302 303
303 // If 'handle_count' is non-zero, iterate through each handle and wait the current thread if 304 using ObjectPtr = Kernel::SharedPtr<Kernel::WaitObject>;
304 // necessary 305
305 if (handle_count != 0) { 306 std::vector<ObjectPtr> objects(handle_count);
306 bool selected = false; // True once an object has been selected 307
307 308 for (int i = 0; i < handle_count; ++i) {
308 Kernel::SharedPtr<Kernel::WaitObject> wait_object; 309 auto object = Kernel::g_handle_table.GetWaitObject(handles[i]);
309 310 if (object == nullptr)
310 for (int i = 0; i < handle_count; ++i) { 311 return ERR_INVALID_HANDLE;
311 auto object = Kernel::g_handle_table.GetWaitObject(handles[i]); 312 objects[i] = object;
312 if (object == nullptr)
313 return ERR_INVALID_HANDLE;
314
315 // Check if the current thread should wait on this object...
316 if (object->ShouldWait()) {
317
318 // Check we are waiting on all objects...
319 if (wait_all)
320 // Wait the thread
321 wait_thread = true;
322 } else {
323 // Do not wait on this object, check if this object should be selected...
324 if (!wait_all && (!selected || (wait_object == object && was_waiting))) {
325 // Do not wait the thread
326 wait_thread = false;
327 handle_index = i;
328 wait_object = object;
329 selected = true;
330 }
331 }
332 }
333 } else {
334 // If no handles were passed in, put the thread to sleep only when 'wait_all' is false
335 // NOTE: This should deadlock the current thread if no timeout was specified
336 if (!wait_all) {
337 wait_thread = true;
338 }
339 } 313 }
340 314
341 SCOPE_EXIT({ 315 // Clear the mapping of wait object indices
342 HLE::Reschedule("WaitSynchronizationN"); 316 thread->wait_objects_index.clear();
343 }); // Reschedule after putting the threads to sleep. 317
318 if (!wait_all) {
319 // Find the first object that is acquireable in the provided list of objects
320 auto itr = std::find_if(objects.begin(), objects.end(), [](const ObjectPtr& object) {
321 return !object->ShouldWait();
322 });
323
324 if (itr != objects.end()) {
325 // We found a ready object, acquire it and set the result value
326 ObjectPtr object = *itr;
327 object->Acquire();
328 *out = std::distance(objects.begin(), itr);
329 return RESULT_SUCCESS;
330 }
331
332 // No objects were ready to be acquired, prepare to suspend the thread.
333
334 // If a timeout value of 0 was provided, just return the Timeout error code instead of suspending the thread.
335 if (nano_seconds == 0) {
336 return ResultCode(ErrorDescription::Timeout, ErrorModule::OS,
337 ErrorSummary::StatusChanged,
338 ErrorLevel::Info);
339 }
344 340
345 // If thread should wait, then set its state to waiting 341 // Put the thread to sleep
346 if (wait_thread) { 342 thread->status = THREADSTATUS_WAIT_SYNCH;
347 343
348 // Actually wait the current thread on each object if we decided to wait... 344 // Clear the thread's waitlist, we won't use it for wait_all = false
349 std::vector<SharedPtr<Kernel::WaitObject>> wait_objects; 345 thread->wait_objects.clear();
350 wait_objects.reserve(handle_count);
351 346
352 for (int i = 0; i < handle_count; ++i) { 347 // Add the thread to each of the objects' waiting threads.
353 auto object = Kernel::g_handle_table.GetWaitObject(handles[i]); 348 for (int i = 0; i < objects.size(); ++i) {
354 object->AddWaitingThread(Kernel::GetCurrentThread()); 349 ObjectPtr object = objects[i];
355 wait_objects.push_back(object); 350 // Set the index of this object in the mapping of Objects -> index for this thread.
351 thread->wait_objects_index[object->GetObjectId()] = i;
352 object->AddWaitingThread(thread);
353 // TODO(Subv): Perform things like update the mutex lock owner's priority to prevent priority inversion.
356 } 354 }
357 355
358 Kernel::WaitCurrentThread_WaitSynchronization(std::move(wait_objects), true, wait_all); 356 // Note: If no handles and no timeout were given, then the thread will deadlock, this is consistent with hardware behavior.
359 357
360 // Create an event to wake the thread up after the specified nanosecond delay has passed 358 // Create an event to wake the thread up after the specified nanosecond delay has passed
361 Kernel::GetCurrentThread()->WakeAfterDelay(nano_seconds); 359 thread->WakeAfterDelay(nano_seconds);
362 360
363 // NOTE: output of this SVC will be set later depending on how the thread resumes 361 // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a signal in one of its wait objects.
364 return HLE::RESULT_INVALID; 362 // Otherwise we retain the default value of timeout, and -1 in the out parameter
365 } 363 thread->wait_set_output = true;
364 *out = -1;
365 return ResultCode(ErrorDescription::Timeout, ErrorModule::OS,
366 ErrorSummary::StatusChanged,
367 ErrorLevel::Info);
368 } else {
369 bool all_available = std::all_of(objects.begin(), objects.end(), [](const ObjectPtr& object) {
370 return !object->ShouldWait();
371 });
372 if (all_available) {
373 // We can acquire all objects right now, do so.
374 for (auto object : objects)
375 object->Acquire();
376 // Note: In this case, the `out` parameter is not set, and retains whatever value it had before.
377 return RESULT_SUCCESS;
378 }
366 379
367 // Acquire objects if we did not wait... 380 // Not all objects were available right now, prepare to suspend the thread.
368 for (int i = 0; i < handle_count; ++i) {
369 auto object = Kernel::g_handle_table.GetWaitObject(handles[i]);
370 381
371 // Acquire the object if it is not waiting... 382 // If a timeout value of 0 was provided, just return the Timeout error code instead of suspending the thread.
372 if (!object->ShouldWait()) { 383 if (nano_seconds == 0) {
373 object->Acquire(); 384 return ResultCode(ErrorDescription::Timeout, ErrorModule::OS,
385 ErrorSummary::StatusChanged,
386 ErrorLevel::Info);
387 }
388
389 // Put the thread to sleep
390 thread->status = THREADSTATUS_WAIT_SYNCH;
374 391
375 // If this was the first non-waiting object and 'wait_all' is false, don't acquire 392 // Set the thread's waitlist to the list of objects passed to WaitSynchronizationN
376 // any other objects 393 thread->wait_objects = objects;
377 if (!wait_all) 394
378 break; 395 // Add the thread to each of the objects' waiting threads.
396 for (auto object : objects) {
397 object->AddWaitingThread(thread);
398 // TODO(Subv): Perform things like update the mutex lock owner's priority to prevent priority inversion.
379 } 399 }
380 }
381 400
382 // TODO(bunnei): If 'wait_all' is true, this is probably wrong. However, real hardware does 401 // Create an event to wake the thread up after the specified nanosecond delay has passed
383 // not seem to set it to any meaningful value. 402 thread->WakeAfterDelay(nano_seconds);
384 *out = handle_count != 0 ? (wait_all ? -1 : handle_index) : 0;
385 403
386 return RESULT_SUCCESS; 404 // This value gets set to -1 by default in this case, it is not modified after this.
405 *out = -1;
406 // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a signal in one of its wait objects.
407 return ResultCode(ErrorDescription::Timeout, ErrorModule::OS,
408 ErrorSummary::StatusChanged,
409 ErrorLevel::Info);
410 }
387} 411}
388 412
389/// Create an address arbiter (to allocate access to shared resources) 413/// Create an address arbiter (to allocate access to shared resources)
@@ -1148,6 +1172,7 @@ void CallSVC(u32 immediate) {
1148 if (info) { 1172 if (info) {
1149 if (info->func) { 1173 if (info->func) {
1150 info->func(); 1174 info->func();
1175 HLE::Reschedule(__func__);
1151 } else { 1176 } else {
1152 LOG_ERROR(Kernel_SVC, "unimplemented SVC function %s(..)", info->name); 1177 LOG_ERROR(Kernel_SVC, "unimplemented SVC function %s(..)", info->name);
1153 } 1178 }