summaryrefslogtreecommitdiff
path: root/src/core/hle/svc.cpp
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/svc.cpp
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/svc.cpp')
-rw-r--r--src/core/hle/svc.cpp181
1 files changed, 103 insertions, 78 deletions
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 }