diff options
| author | 2016-12-16 00:41:22 -0500 | |
|---|---|---|
| committer | 2016-12-16 00:41:22 -0500 | |
| commit | cda7210fade53a96fcba5fe5cd6dfd7b604f8277 (patch) | |
| tree | c4090e3871e717ee4d0a2edd837feffc2c877cb0 /src/core/hle/svc.cpp | |
| parent | Merge pull request #2316 from endrift/macos-gcc (diff) | |
| parent | Fixed the codestyle to match our clang-format rules. (diff) | |
| download | yuzu-cda7210fade53a96fcba5fe5cd6dfd7b604f8277.tar.gz yuzu-cda7210fade53a96fcba5fe5cd6dfd7b604f8277.tar.xz yuzu-cda7210fade53a96fcba5fe5cd6dfd7b604f8277.zip | |
Merge pull request #2260 from Subv/scheduling
Threading: Reworked the way our scheduler works.
Diffstat (limited to 'src/core/hle/svc.cpp')
| -rw-r--r-- | src/core/hle/svc.cpp | 187 |
1 files changed, 107 insertions, 80 deletions
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index e5ba9a484..ef25acc4a 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp | |||
| @@ -43,6 +43,9 @@ const ResultCode ERR_PORT_NAME_TOO_LONG(ErrorDescription(30), ErrorModule::OS, | |||
| 43 | ErrorSummary::InvalidArgument, | 43 | ErrorSummary::InvalidArgument, |
| 44 | ErrorLevel::Usage); // 0xE0E0181E | 44 | ErrorLevel::Usage); // 0xE0E0181E |
| 45 | 45 | ||
| 46 | const ResultCode ERR_SYNC_TIMEOUT(ErrorDescription::Timeout, ErrorModule::OS, | ||
| 47 | ErrorSummary::StatusChanged, ErrorLevel::Info); | ||
| 48 | |||
| 46 | const ResultCode ERR_MISALIGNED_ADDRESS{// 0xE0E01BF1 | 49 | const ResultCode ERR_MISALIGNED_ADDRESS{// 0xE0E01BF1 |
| 47 | ErrorDescription::MisalignedAddress, ErrorModule::OS, | 50 | ErrorDescription::MisalignedAddress, ErrorModule::OS, |
| 48 | ErrorSummary::InvalidArgument, ErrorLevel::Usage}; | 51 | ErrorSummary::InvalidArgument, ErrorLevel::Usage}; |
| @@ -260,27 +263,30 @@ static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) { | |||
| 260 | auto object = Kernel::g_handle_table.GetWaitObject(handle); | 263 | auto object = Kernel::g_handle_table.GetWaitObject(handle); |
| 261 | Kernel::Thread* thread = Kernel::GetCurrentThread(); | 264 | Kernel::Thread* thread = Kernel::GetCurrentThread(); |
| 262 | 265 | ||
| 263 | thread->waitsynch_waited = false; | ||
| 264 | |||
| 265 | if (object == nullptr) | 266 | if (object == nullptr) |
| 266 | return ERR_INVALID_HANDLE; | 267 | return ERR_INVALID_HANDLE; |
| 267 | 268 | ||
| 268 | LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s:%s), nanoseconds=%lld", handle, | 269 | LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s:%s), nanoseconds=%lld", handle, |
| 269 | object->GetTypeName().c_str(), object->GetName().c_str(), nano_seconds); | 270 | object->GetTypeName().c_str(), object->GetName().c_str(), nano_seconds); |
| 270 | 271 | ||
| 271 | HLE::Reschedule(__func__); | ||
| 272 | |||
| 273 | // Check for next thread to schedule | ||
| 274 | if (object->ShouldWait()) { | 272 | if (object->ShouldWait()) { |
| 275 | 273 | ||
| 274 | if (nano_seconds == 0) | ||
| 275 | return ERR_SYNC_TIMEOUT; | ||
| 276 | |||
| 276 | object->AddWaitingThread(thread); | 277 | object->AddWaitingThread(thread); |
| 277 | Kernel::WaitCurrentThread_WaitSynchronization({object}, false, false); | 278 | // TODO(Subv): Perform things like update the mutex lock owner's priority to |
| 279 | // prevent priority inversion. Currently this is done in Mutex::ShouldWait, | ||
| 280 | // but it should be moved to a function that is called from here. | ||
| 281 | thread->status = THREADSTATUS_WAIT_SYNCH; | ||
| 278 | 282 | ||
| 279 | // Create an event to wake the thread up after the specified nanosecond delay has passed | 283 | // Create an event to wake the thread up after the specified nanosecond delay has passed |
| 280 | thread->WakeAfterDelay(nano_seconds); | 284 | thread->WakeAfterDelay(nano_seconds); |
| 281 | 285 | ||
| 282 | // NOTE: output of this SVC will be set later depending on how the thread resumes | 286 | // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread |
| 283 | return HLE::RESULT_INVALID; | 287 | // resumes due to a signal in its wait objects. |
| 288 | // Otherwise we retain the default value of timeout. | ||
| 289 | return ERR_SYNC_TIMEOUT; | ||
| 284 | } | 290 | } |
| 285 | 291 | ||
| 286 | object->Acquire(); | 292 | object->Acquire(); |
| @@ -291,11 +297,7 @@ static ResultCode WaitSynchronization1(Handle handle, s64 nano_seconds) { | |||
| 291 | /// Wait for the given handles to synchronize, timeout after the specified nanoseconds | 297 | /// Wait for the given handles to synchronize, timeout after the specified nanoseconds |
| 292 | static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_count, bool wait_all, | 298 | static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_count, bool wait_all, |
| 293 | s64 nano_seconds) { | 299 | s64 nano_seconds) { |
| 294 | bool wait_thread = !wait_all; | ||
| 295 | int handle_index = 0; | ||
| 296 | Kernel::Thread* thread = Kernel::GetCurrentThread(); | 300 | Kernel::Thread* thread = Kernel::GetCurrentThread(); |
| 297 | bool was_waiting = thread->waitsynch_waited; | ||
| 298 | thread->waitsynch_waited = false; | ||
| 299 | 301 | ||
| 300 | // Check if 'handles' is invalid | 302 | // Check if 'handles' is invalid |
| 301 | if (handles == nullptr) | 303 | if (handles == nullptr) |
| @@ -311,90 +313,113 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou | |||
| 311 | return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS, | 313 | return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS, |
| 312 | ErrorSummary::InvalidArgument, ErrorLevel::Usage); | 314 | ErrorSummary::InvalidArgument, ErrorLevel::Usage); |
| 313 | 315 | ||
| 314 | // If 'handle_count' is non-zero, iterate through each handle and wait the current thread if | 316 | using ObjectPtr = Kernel::SharedPtr<Kernel::WaitObject>; |
| 315 | // necessary | 317 | std::vector<ObjectPtr> objects(handle_count); |
| 316 | if (handle_count != 0) { | 318 | |
| 317 | bool selected = false; // True once an object has been selected | 319 | for (int i = 0; i < handle_count; ++i) { |
| 318 | 320 | auto object = Kernel::g_handle_table.GetWaitObject(handles[i]); | |
| 319 | Kernel::SharedPtr<Kernel::WaitObject> wait_object; | 321 | if (object == nullptr) |
| 320 | 322 | return ERR_INVALID_HANDLE; | |
| 321 | for (int i = 0; i < handle_count; ++i) { | 323 | objects[i] = object; |
| 322 | auto object = Kernel::g_handle_table.GetWaitObject(handles[i]); | ||
| 323 | if (object == nullptr) | ||
| 324 | return ERR_INVALID_HANDLE; | ||
| 325 | |||
| 326 | // Check if the current thread should wait on this object... | ||
| 327 | if (object->ShouldWait()) { | ||
| 328 | |||
| 329 | // Check we are waiting on all objects... | ||
| 330 | if (wait_all) | ||
| 331 | // Wait the thread | ||
| 332 | wait_thread = true; | ||
| 333 | } else { | ||
| 334 | // Do not wait on this object, check if this object should be selected... | ||
| 335 | if (!wait_all && (!selected || (wait_object == object && was_waiting))) { | ||
| 336 | // Do not wait the thread | ||
| 337 | wait_thread = false; | ||
| 338 | handle_index = i; | ||
| 339 | wait_object = object; | ||
| 340 | selected = true; | ||
| 341 | } | ||
| 342 | } | ||
| 343 | } | ||
| 344 | } else { | ||
| 345 | // If no handles were passed in, put the thread to sleep only when 'wait_all' is false | ||
| 346 | // NOTE: This should deadlock the current thread if no timeout was specified | ||
| 347 | if (!wait_all) { | ||
| 348 | wait_thread = true; | ||
| 349 | } | ||
| 350 | } | 324 | } |
| 351 | 325 | ||
| 352 | SCOPE_EXIT({ | 326 | // Clear the mapping of wait object indices. |
| 353 | HLE::Reschedule("WaitSynchronizationN"); | 327 | // We don't want any lingering state in this map. |
| 354 | }); // Reschedule after putting the threads to sleep. | 328 | // It will be repopulated later in the wait_all = false case. |
| 329 | thread->wait_objects_index.clear(); | ||
| 330 | |||
| 331 | if (wait_all) { | ||
| 332 | bool all_available = | ||
| 333 | std::all_of(objects.begin(), objects.end(), | ||
| 334 | [](const ObjectPtr& object) { return !object->ShouldWait(); }); | ||
| 335 | if (all_available) { | ||
| 336 | // We can acquire all objects right now, do so. | ||
| 337 | for (auto& object : objects) | ||
| 338 | object->Acquire(); | ||
| 339 | // Note: In this case, the `out` parameter is not set, | ||
| 340 | // and retains whatever value it had before. | ||
| 341 | return RESULT_SUCCESS; | ||
| 342 | } | ||
| 343 | |||
| 344 | // Not all objects were available right now, prepare to suspend the thread. | ||
| 355 | 345 | ||
| 356 | // If thread should wait, then set its state to waiting | 346 | // If a timeout value of 0 was provided, just return the Timeout error code instead of |
| 357 | if (wait_thread) { | 347 | // suspending the thread. |
| 348 | if (nano_seconds == 0) | ||
| 349 | return ERR_SYNC_TIMEOUT; | ||
| 358 | 350 | ||
| 359 | // Actually wait the current thread on each object if we decided to wait... | 351 | // Put the thread to sleep |
| 360 | std::vector<SharedPtr<Kernel::WaitObject>> wait_objects; | 352 | thread->status = THREADSTATUS_WAIT_SYNCH; |
| 361 | wait_objects.reserve(handle_count); | ||
| 362 | 353 | ||
| 363 | for (int i = 0; i < handle_count; ++i) { | 354 | // Add the thread to each of the objects' waiting threads. |
| 364 | auto object = Kernel::g_handle_table.GetWaitObject(handles[i]); | 355 | for (auto& object : objects) { |
| 365 | object->AddWaitingThread(Kernel::GetCurrentThread()); | 356 | object->AddWaitingThread(thread); |
| 366 | wait_objects.push_back(object); | 357 | // TODO(Subv): Perform things like update the mutex lock owner's priority to |
| 358 | // prevent priority inversion. Currently this is done in Mutex::ShouldWait, | ||
| 359 | // but it should be moved to a function that is called from here. | ||
| 367 | } | 360 | } |
| 368 | 361 | ||
| 369 | Kernel::WaitCurrentThread_WaitSynchronization(std::move(wait_objects), true, wait_all); | 362 | // Set the thread's waitlist to the list of objects passed to WaitSynchronizationN |
| 363 | thread->wait_objects = std::move(objects); | ||
| 370 | 364 | ||
| 371 | // Create an event to wake the thread up after the specified nanosecond delay has passed | 365 | // Create an event to wake the thread up after the specified nanosecond delay has passed |
| 372 | Kernel::GetCurrentThread()->WakeAfterDelay(nano_seconds); | 366 | thread->WakeAfterDelay(nano_seconds); |
| 373 | |||
| 374 | // NOTE: output of this SVC will be set later depending on how the thread resumes | ||
| 375 | return HLE::RESULT_INVALID; | ||
| 376 | } | ||
| 377 | 367 | ||
| 378 | // Acquire objects if we did not wait... | 368 | // This value gets set to -1 by default in this case, it is not modified after this. |
| 379 | for (int i = 0; i < handle_count; ++i) { | 369 | *out = -1; |
| 380 | auto object = Kernel::g_handle_table.GetWaitObject(handles[i]); | 370 | // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to |
| 371 | // a signal in one of its wait objects. | ||
| 372 | return ERR_SYNC_TIMEOUT; | ||
| 373 | } else { | ||
| 374 | // Find the first object that is acquirable in the provided list of objects | ||
| 375 | auto itr = std::find_if(objects.begin(), objects.end(), | ||
| 376 | [](const ObjectPtr& object) { return !object->ShouldWait(); }); | ||
| 381 | 377 | ||
| 382 | // Acquire the object if it is not waiting... | 378 | if (itr != objects.end()) { |
| 383 | if (!object->ShouldWait()) { | 379 | // We found a ready object, acquire it and set the result value |
| 380 | Kernel::WaitObject* object = itr->get(); | ||
| 384 | object->Acquire(); | 381 | object->Acquire(); |
| 382 | *out = std::distance(objects.begin(), itr); | ||
| 383 | return RESULT_SUCCESS; | ||
| 384 | } | ||
| 385 | |||
| 386 | // No objects were ready to be acquired, prepare to suspend the thread. | ||
| 387 | |||
| 388 | // If a timeout value of 0 was provided, just return the Timeout error code instead of | ||
| 389 | // suspending the thread. | ||
| 390 | if (nano_seconds == 0) | ||
| 391 | return ERR_SYNC_TIMEOUT; | ||
| 392 | |||
| 393 | // Put the thread to sleep | ||
| 394 | thread->status = THREADSTATUS_WAIT_SYNCH; | ||
| 385 | 395 | ||
| 386 | // If this was the first non-waiting object and 'wait_all' is false, don't acquire | 396 | // Clear the thread's waitlist, we won't use it for wait_all = false |
| 387 | // any other objects | 397 | thread->wait_objects.clear(); |
| 388 | if (!wait_all) | 398 | |
| 389 | break; | 399 | // Add the thread to each of the objects' waiting threads. |
| 400 | for (size_t i = 0; i < objects.size(); ++i) { | ||
| 401 | Kernel::WaitObject* object = objects[i].get(); | ||
| 402 | // Set the index of this object in the mapping of Objects -> index for this thread. | ||
| 403 | thread->wait_objects_index[object->GetObjectId()] = static_cast<int>(i); | ||
| 404 | object->AddWaitingThread(thread); | ||
| 405 | // TODO(Subv): Perform things like update the mutex lock owner's priority to | ||
| 406 | // prevent priority inversion. Currently this is done in Mutex::ShouldWait, | ||
| 407 | // but it should be moved to a function that is called from here. | ||
| 390 | } | 408 | } |
| 391 | } | ||
| 392 | 409 | ||
| 393 | // TODO(bunnei): If 'wait_all' is true, this is probably wrong. However, real hardware does | 410 | // Note: If no handles and no timeout were given, then the thread will deadlock, this is |
| 394 | // not seem to set it to any meaningful value. | 411 | // consistent with hardware behavior. |
| 395 | *out = handle_count != 0 ? (wait_all ? -1 : handle_index) : 0; | ||
| 396 | 412 | ||
| 397 | return RESULT_SUCCESS; | 413 | // Create an event to wake the thread up after the specified nanosecond delay has passed |
| 414 | thread->WakeAfterDelay(nano_seconds); | ||
| 415 | |||
| 416 | // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a | ||
| 417 | // signal in one of its wait objects. | ||
| 418 | // Otherwise we retain the default value of timeout, and -1 in the out parameter | ||
| 419 | thread->wait_set_output = true; | ||
| 420 | *out = -1; | ||
| 421 | return ERR_SYNC_TIMEOUT; | ||
| 422 | } | ||
| 398 | } | 423 | } |
| 399 | 424 | ||
| 400 | /// Create an address arbiter (to allocate access to shared resources) | 425 | /// Create an address arbiter (to allocate access to shared resources) |
| @@ -1159,6 +1184,8 @@ void CallSVC(u32 immediate) { | |||
| 1159 | if (info) { | 1184 | if (info) { |
| 1160 | if (info->func) { | 1185 | if (info->func) { |
| 1161 | info->func(); | 1186 | info->func(); |
| 1187 | // TODO(Subv): Not all service functions should cause a reschedule in all cases. | ||
| 1188 | HLE::Reschedule(__func__); | ||
| 1162 | } else { | 1189 | } else { |
| 1163 | LOG_ERROR(Kernel_SVC, "unimplemented SVC function %s(..)", info->name); | 1190 | LOG_ERROR(Kernel_SVC, "unimplemented SVC function %s(..)", info->name); |
| 1164 | } | 1191 | } |