diff options
Diffstat (limited to 'src/core')
33 files changed, 380 insertions, 291 deletions
diff --git a/src/core/core.cpp b/src/core/core.cpp index ee5237096..202cd332b 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -159,6 +159,7 @@ void System::Shutdown() { Kernel::Shutdown(); HW::Shutdown(); CoreTiming::Shutdown(); + cpu_core.reset(); LOG_DEBUG(Core, "Shutdown OK"); } diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index d88e25073..5cf45ada5 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -57,7 +57,6 @@ const u32 SIGTERM = 15; const u32 MSG_WAITALL = 8; #endif -const u32 R0_REGISTER = 0; const u32 R15_REGISTER = 15; const u32 CPSR_REGISTER = 25; const u32 FPSCR_REGISTER = 58; @@ -816,10 +815,6 @@ static void RemoveBreakpoint() { auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); - start_offset = addr_pos + 1; - u32 len = - HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset)); - if (type == BreakpointType::Access) { // Access is made up of Read and Write types, so add both breakpoints type = BreakpointType::Read; diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp deleted file mode 100644 index d73d98a70..000000000 --- a/src/core/hle/hle.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/assert.h" -#include "common/logging/log.h" -#include "core/arm/arm_interface.h" -#include "core/core.h" -#include "core/hle/hle.h" -#include "core/hle/service/service.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace { - -bool reschedule; ///< If true, immediately reschedules the CPU to a new thread -} - -namespace HLE { - -void Reschedule(const char* reason) { - DEBUG_ASSERT_MSG(reason != nullptr && strlen(reason) < 256, - "Reschedule: Invalid or too long reason."); - - // TODO(bunnei): It seems that games depend on some CPU execution time elapsing during HLE - // routines. This simulates that time by artificially advancing the number of CPU "ticks". - // The value was chosen empirically, it seems to work well enough for everything tested, but - // is likely not ideal. We should find a more accurate way to simulate timing with HLE. - Core::AppCore().AddTicks(4000); - - Core::AppCore().PrepareReschedule(); - - reschedule = true; -} - -bool IsReschedulePending() { - return reschedule; -} - -void DoneRescheduling() { - reschedule = false; -} - -void Init() { - Service::Init(); - - reschedule = false; - - LOG_DEBUG(Kernel, "initialized OK"); -} - -void Shutdown() { - Service::Shutdown(); - - LOG_DEBUG(Kernel, "shutdown OK"); -} - -} // namespace diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp index 3e116e3df..e1f42af05 100644 --- a/src/core/hle/kernel/event.cpp +++ b/src/core/hle/kernel/event.cpp @@ -30,12 +30,12 @@ SharedPtr<Event> Event::Create(ResetType reset_type, std::string name) { return evt; } -bool Event::ShouldWait() { +bool Event::ShouldWait(Thread* thread) const { return !signaled; } -void Event::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Event::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); // Release the event if it's not sticky... if (reset_type != ResetType::Sticky) diff --git a/src/core/hle/kernel/event.h b/src/core/hle/kernel/event.h index 8dcd23edb..39452bf33 100644 --- a/src/core/hle/kernel/event.h +++ b/src/core/hle/kernel/event.h @@ -35,8 +35,8 @@ public: bool signaled; ///< Whether the event has already been signaled std::string name; ///< Name of event (optional) - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; void Signal(); void Clear(); diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 1db8e102f..f599916f0 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -3,7 +3,6 @@ // Refer to the license.txt file included. #include <algorithm> -#include <boost/range/algorithm_ext/erase.hpp> #include "common/assert.h" #include "common/logging/log.h" #include "core/hle/config_mem.h" @@ -28,32 +27,39 @@ void WaitObject::AddWaitingThread(SharedPtr<Thread> thread) { void WaitObject::RemoveWaitingThread(Thread* thread) { auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); + // If a thread passed multiple handles to the same object, + // the kernel might attempt to remove the thread from the object's + // waiting threads list multiple times. if (itr != waiting_threads.end()) waiting_threads.erase(itr); } SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { - // Remove the threads that are ready or already running from our waitlist - boost::range::remove_erase_if(waiting_threads, [](const SharedPtr<Thread>& thread) { - return thread->status == THREADSTATUS_RUNNING || thread->status == THREADSTATUS_READY || - thread->status == THREADSTATUS_DEAD; - }); - - // TODO(Subv): This call should be performed inside the loop below to check if an object can be - // acquired by a particular thread. This is useful for things like recursive locking of Mutexes. - if (ShouldWait()) - return nullptr; - Thread* candidate = nullptr; s32 candidate_priority = THREADPRIO_LOWEST + 1; for (const auto& thread : waiting_threads) { + // The list of waiting threads must not contain threads that are not waiting to be awakened. + ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || + thread->status == THREADSTATUS_WAIT_SYNCH_ALL, + "Inconsistent thread statuses in waiting_threads"); + if (thread->current_priority >= candidate_priority) continue; - bool ready_to_run = - std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), - [](const SharedPtr<WaitObject>& object) { return object->ShouldWait(); }); + if (ShouldWait(thread.get())) + continue; + + // A thread is ready to run if it's either in THREADSTATUS_WAIT_SYNCH_ANY or + // in THREADSTATUS_WAIT_SYNCH_ALL and the rest of the objects it is waiting on are ready. + bool ready_to_run = true; + if (thread->status == THREADSTATUS_WAIT_SYNCH_ALL) { + ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), + [&thread](const SharedPtr<WaitObject>& object) { + return object->ShouldWait(thread.get()); + }); + } + if (ready_to_run) { candidate = thread.get(); candidate_priority = thread->current_priority; @@ -66,7 +72,7 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { void WaitObject::WakeupAllWaitingThreads() { while (auto thread = GetHighestPriorityReadyThread()) { if (!thread->IsSleepingOnWaitAll()) { - Acquire(); + Acquire(thread.get()); // Set the output index of the WaitSynchronizationN call to the index of this object. if (thread->wait_set_output) { thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(this)); @@ -74,18 +80,17 @@ void WaitObject::WakeupAllWaitingThreads() { } } else { for (auto& object : thread->wait_objects) { - object->Acquire(); - object->RemoveWaitingThread(thread.get()); + object->Acquire(thread.get()); } // Note: This case doesn't update the output index of WaitSynchronizationN. - // Clear the thread's waitlist - thread->wait_objects.clear(); } + for (auto& object : thread->wait_objects) + object->RemoveWaitingThread(thread.get()); + thread->wait_objects.clear(); + thread->SetWaitSynchronizationResult(RESULT_SUCCESS); thread->ResumeFromWait(); - // Note: Removing the thread from the object's waitlist will be - // done by GetHighestPriorityReadyThread. } } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 9503e7d04..05097824b 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -132,25 +132,26 @@ using SharedPtr = boost::intrusive_ptr<T>; class WaitObject : public Object { public: /** - * Check if the current thread should wait until the object is available + * Check if the specified thread should wait until the object is available + * @param thread The thread about which we're deciding. * @return True if the current thread should wait due to this object being unavailable */ - virtual bool ShouldWait() = 0; + virtual bool ShouldWait(Thread* thread) const = 0; - /// Acquire/lock the object if it is available - virtual void Acquire() = 0; + /// Acquire/lock the object for the specified thread if it is available + virtual void Acquire(Thread* thread) = 0; /** * Add a thread to wait on this object * @param thread Pointer to thread to add */ - void AddWaitingThread(SharedPtr<Thread> thread); + virtual void AddWaitingThread(SharedPtr<Thread> thread); /** * Removes a thread from waiting on this object (e.g. if it was resumed already) * @param thread Pointer to thread to remove */ - void RemoveWaitingThread(Thread* thread); + virtual void RemoveWaitingThread(Thread* thread); /** * Wake up all threads waiting on this object that can be awoken, in priority order, diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index 736944bae..cef961289 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp @@ -6,26 +6,18 @@ #include <vector> #include <boost/range/algorithm_ext/erase.hpp> #include "common/assert.h" +#include "core/core.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/thread.h" namespace Kernel { -/** - * Resumes a thread waiting for the specified mutex - * @param mutex The mutex that some thread is waiting on - */ -static void ResumeWaitingThread(Mutex* mutex) { - // Reset mutex lock thread handle, nothing is waiting - mutex->lock_count = 0; - mutex->holding_thread = nullptr; - mutex->WakeupAllWaitingThreads(); -} - void ReleaseThreadMutexes(Thread* thread) { for (auto& mtx : thread->held_mutexes) { - ResumeWaitingThread(mtx.get()); + mtx->lock_count = 0; + mtx->holding_thread = nullptr; + mtx->WakeupAllWaitingThreads(); } thread->held_mutexes.clear(); } @@ -40,52 +32,74 @@ SharedPtr<Mutex> Mutex::Create(bool initial_locked, std::string name) { mutex->name = std::move(name); mutex->holding_thread = nullptr; - // Acquire mutex with current thread if initialized as locked... + // Acquire mutex with current thread if initialized as locked if (initial_locked) - mutex->Acquire(); + mutex->Acquire(GetCurrentThread()); return mutex; } -bool Mutex::ShouldWait() { - auto thread = GetCurrentThread(); - bool wait = lock_count > 0 && holding_thread != thread; - - // If the holding thread of the mutex is lower priority than this thread, that thread should - // temporarily inherit this thread's priority - if (wait && thread->current_priority < holding_thread->current_priority) - holding_thread->BoostPriority(thread->current_priority); - - return wait; -} - -void Mutex::Acquire() { - Acquire(GetCurrentThread()); +bool Mutex::ShouldWait(Thread* thread) const { + return lock_count > 0 && thread != holding_thread; } -void Mutex::Acquire(SharedPtr<Thread> thread) { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Mutex::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); - // Actually "acquire" the mutex only if we don't already have it... + // Actually "acquire" the mutex only if we don't already have it if (lock_count == 0) { + priority = thread->current_priority; thread->held_mutexes.insert(this); - holding_thread = std::move(thread); + holding_thread = thread; + thread->UpdatePriority(); + Core::System::GetInstance().PrepareReschedule(); } lock_count++; } void Mutex::Release() { - // Only release if the mutex is held... + // Only release if the mutex is held if (lock_count > 0) { lock_count--; - // Yield to the next thread only if we've fully released the mutex... + // Yield to the next thread only if we've fully released the mutex if (lock_count == 0) { holding_thread->held_mutexes.erase(this); - ResumeWaitingThread(this); + holding_thread->UpdatePriority(); + holding_thread = nullptr; + WakeupAllWaitingThreads(); + Core::System::GetInstance().PrepareReschedule(); } } } +void Mutex::AddWaitingThread(SharedPtr<Thread> thread) { + WaitObject::AddWaitingThread(thread); + thread->pending_mutexes.insert(this); + UpdatePriority(); +} + +void Mutex::RemoveWaitingThread(Thread* thread) { + WaitObject::RemoveWaitingThread(thread); + thread->pending_mutexes.erase(this); + UpdatePriority(); +} + +void Mutex::UpdatePriority() { + if (!holding_thread) + return; + + s32 best_priority = THREADPRIO_LOWEST; + for (auto& waiter : GetWaitingThreads()) { + if (waiter->current_priority < best_priority) + best_priority = waiter->current_priority; + } + + if (best_priority != priority) { + priority = best_priority; + holding_thread->UpdatePriority(); + } +} + } // namespace diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h index 53c3dc1f1..c57adf400 100644 --- a/src/core/hle/kernel/mutex.h +++ b/src/core/hle/kernel/mutex.h @@ -35,17 +35,22 @@ public: } int lock_count; ///< Number of times the mutex has been acquired + u32 priority; ///< The priority of the mutex, used for priority inheritance. std::string name; ///< Name of mutex (optional) SharedPtr<Thread> holding_thread; ///< Thread that has acquired the mutex - bool ShouldWait() override; - void Acquire() override; - /** - * Acquires the specified mutex for the specified thread - * @param thread Thread that will acquire the mutex + * Elevate the mutex priority to the best priority + * among the priorities of all its waiting threads. */ - void Acquire(SharedPtr<Thread> thread); + void UpdatePriority(); + + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; + + void AddWaitingThread(SharedPtr<Thread> thread) override; + void RemoveWaitingThread(Thread* thread) override; + void Release(); private: diff --git a/src/core/hle/kernel/semaphore.cpp b/src/core/hle/kernel/semaphore.cpp index bf7600780..8bda2f75d 100644 --- a/src/core/hle/kernel/semaphore.cpp +++ b/src/core/hle/kernel/semaphore.cpp @@ -30,12 +30,13 @@ ResultVal<SharedPtr<Semaphore>> Semaphore::Create(s32 initial_count, s32 max_cou return MakeResult<SharedPtr<Semaphore>>(std::move(semaphore)); } -bool Semaphore::ShouldWait() { +bool Semaphore::ShouldWait(Thread* thread) const { return available_count <= 0; } -void Semaphore::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Semaphore::Acquire(Thread* thread) { + if (available_count <= 0) + return; --available_count; } diff --git a/src/core/hle/kernel/semaphore.h b/src/core/hle/kernel/semaphore.h index e01908a25..cde94f7cc 100644 --- a/src/core/hle/kernel/semaphore.h +++ b/src/core/hle/kernel/semaphore.h @@ -39,8 +39,8 @@ public: s32 available_count; ///< Number of free slots left in the semaphore std::string name; ///< Name of semaphore (optional) - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; /** * Releases a certain number of slots from a semaphore. diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp index 6c19aa7c0..fd3bbbcad 100644 --- a/src/core/hle/kernel/server_port.cpp +++ b/src/core/hle/kernel/server_port.cpp @@ -14,13 +14,13 @@ namespace Kernel { ServerPort::ServerPort() {} ServerPort::~ServerPort() {} -bool ServerPort::ShouldWait() { +bool ServerPort::ShouldWait(Thread* thread) const { // If there are no pending sessions, we wait until a new one is added. return pending_sessions.size() == 0; } -void ServerPort::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void ServerPort::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); } std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair( diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index b0f8df62c..6f8bdb6a9 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -53,8 +53,8 @@ public: /// ServerSessions created from this port inherit a reference to this handler. std::shared_ptr<Service::SessionRequestHandler> hle_handler; - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; private: ServerPort(); diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index 146458c1c..9447ff236 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -29,12 +29,12 @@ ResultVal<SharedPtr<ServerSession>> ServerSession::Create( return MakeResult<SharedPtr<ServerSession>>(std::move(server_session)); } -bool ServerSession::ShouldWait() { +bool ServerSession::ShouldWait(Thread* thread) const { return !signaled; } -void ServerSession::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void ServerSession::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); signaled = false; } diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index 458284a5d..c088b9a19 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -57,9 +57,9 @@ public: */ ResultCode HandleSyncRequest(); - bool ShouldWait() override; + bool ShouldWait(Thread* thread) const override; - void Acquire() override; + void Acquire(Thread* thread) override; std::string name; ///< The name of this session (optional) bool signaled; ///< Whether there's new data available to this ServerSession diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 5fb95dada..8c6fbcd04 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -27,12 +27,12 @@ namespace Kernel { /// Event type for the thread wake up event static int ThreadWakeupEventType; -bool Thread::ShouldWait() { +bool Thread::ShouldWait(Thread* thread) const { return status != THREADSTATUS_DEAD; } -void Thread::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Thread::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); } // TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future, allowing @@ -66,20 +66,6 @@ Thread* GetCurrentThread() { } /** - * Check if a thread is waiting on the specified wait object - * @param thread The thread to test - * @param wait_object The object to test against - * @return True if the thread is waiting, false otherwise - */ -static bool CheckWait_WaitObject(const Thread* thread, WaitObject* wait_object) { - if (thread->status != THREADSTATUS_WAIT_SYNCH) - return false; - - auto itr = std::find(thread->wait_objects.begin(), thread->wait_objects.end(), wait_object); - return itr != thread->wait_objects.end(); -} - -/** * Check if the specified thread is waiting on the specified address to be arbitrated * @param thread The thread to test * @param wait_address The address to test against @@ -90,9 +76,6 @@ static bool CheckWait_AddressArbiter(const Thread* thread, VAddr wait_address) { } void Thread::Stop() { - // Release all the mutexes that this thread holds - ReleaseThreadMutexes(this); - // Cancel any outstanding wakeup events for this thread CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle); wakeup_callback_handle_table.Close(callback_handle); @@ -114,6 +97,9 @@ void Thread::Stop() { } wait_objects.clear(); + // Release all the mutexes that this thread holds + ReleaseThreadMutexes(this); + // Mark the TLS slot in the thread's page as free. u32 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE; u32 tls_slot = @@ -155,28 +141,6 @@ void ArbitrateAllThreads(u32 address) { } } -/// Boost low priority threads (temporarily) that have been starved -static void PriorityBoostStarvedThreads() { - u64 current_ticks = CoreTiming::GetTicks(); - - for (auto& thread : thread_list) { - // TODO(bunnei): Threads that have been waiting to be scheduled for `boost_ticks` (or - // longer) will have their priority temporarily adjusted to 1 higher than the highest - // priority thread to prevent thread starvation. This general behavior has been verified - // on hardware. However, this is almost certainly not perfect, and the real CTR OS scheduler - // should probably be reversed to verify this. - - const u64 boost_timeout = 2000000; // Boost threads that have been ready for > this long - - u64 delta = current_ticks - thread->last_running_ticks; - - if (thread->status == THREADSTATUS_READY && delta > boost_timeout) { - const s32 priority = std::max(ready_queue.get_first()->current_priority - 1, 0); - thread->BoostPriority(priority); - } - } -} - /** * Switches the CPU's active thread context to that of the specified thread * @param new_thread The thread to switch to @@ -199,8 +163,8 @@ static void SwitchContext(Thread* new_thread) { // Load context of new thread if (new_thread) { - DEBUG_ASSERT_MSG(new_thread->status == THREADSTATUS_READY, - "Thread must be ready to become running."); + ASSERT_MSG(new_thread->status == THREADSTATUS_READY, + "Thread must be ready to become running."); // Cancel any outstanding wakeup events for this thread CoreTiming::UnscheduleEvent(ThreadWakeupEventType, new_thread->callback_handle); @@ -210,9 +174,6 @@ static void SwitchContext(Thread* new_thread) { ready_queue.remove(new_thread->current_priority, new_thread); new_thread->status = THREADSTATUS_RUNNING; - // Restores thread to its nominal priority if it has been temporarily changed - new_thread->current_priority = new_thread->nominal_priority; - Core::CPU().LoadContext(new_thread->context); Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); } else { @@ -248,14 +209,6 @@ void WaitCurrentThread_Sleep() { thread->status = THREADSTATUS_WAIT_SLEEP; } -void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects, - bool wait_set_output) { - Thread* thread = GetCurrentThread(); - thread->wait_set_output = wait_set_output; - thread->wait_objects = std::move(wait_objects); - thread->status = THREADSTATUS_WAIT_SYNCH; -} - void WaitCurrentThread_ArbitrateAddress(VAddr wait_address) { Thread* thread = GetCurrentThread(); thread->wait_address = wait_address; @@ -281,7 +234,8 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) { return; } - if (thread->status == THREADSTATUS_WAIT_SYNCH || thread->status == THREADSTATUS_WAIT_ARB) { + if (thread->status == THREADSTATUS_WAIT_SYNCH_ANY || + thread->status == THREADSTATUS_WAIT_SYNCH_ALL || thread->status == THREADSTATUS_WAIT_ARB) { thread->wait_set_output = false; // Remove the thread from each of its waiting objects' waitlists for (auto& object : thread->wait_objects) @@ -305,8 +259,11 @@ void Thread::WakeAfterDelay(s64 nanoseconds) { } void Thread::ResumeFromWait() { + ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects"); + switch (status) { - case THREADSTATUS_WAIT_SYNCH: + case THREADSTATUS_WAIT_SYNCH_ALL: + case THREADSTATUS_WAIT_SYNCH_ANY: case THREADSTATUS_WAIT_ARB: case THREADSTATUS_WAIT_SLEEP: break; @@ -515,8 +472,21 @@ void Thread::SetPriority(s32 priority) { nominal_priority = current_priority = priority; } +void Thread::UpdatePriority() { + s32 best_priority = nominal_priority; + for (auto& mutex : held_mutexes) { + if (mutex->priority < best_priority) + best_priority = mutex->priority; + } + BoostPriority(best_priority); +} + void Thread::BoostPriority(s32 priority) { - ready_queue.move(this, current_priority, priority); + // If thread was ready, adjust queues + if (status == THREADSTATUS_READY) + ready_queue.move(this, current_priority, priority); + else + ready_queue.prepare(priority); current_priority = priority; } @@ -538,9 +508,11 @@ SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) { return thread; } -void Reschedule() { - PriorityBoostStarvedThreads(); +bool HaveReadyThreads() { + return ready_queue.get_first() != nullptr; +} +void Reschedule() { Thread* cur = GetCurrentThread(); Thread* next = PopNextReadyThread(); @@ -563,6 +535,12 @@ void Thread::SetWaitSynchronizationOutput(s32 output) { context.cpu_registers[1] = output; } +s32 Thread::GetWaitObjectIndex(WaitObject* object) const { + ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything"); + auto match = std::find(wait_objects.rbegin(), wait_objects.rend(), object); + return std::distance(match, wait_objects.rend()) - 1; +} + //////////////////////////////////////////////////////////////////////////////////////////////////// void ThreadingInit() { diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index c77ac644d..c557a2279 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -31,13 +31,14 @@ enum ThreadProcessorId : s32 { }; enum ThreadStatus { - THREADSTATUS_RUNNING, ///< Currently running - THREADSTATUS_READY, ///< Ready to run - THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter - THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC - THREADSTATUS_WAIT_SYNCH, ///< Waiting due to a WaitSynchronization SVC - THREADSTATUS_DORMANT, ///< Created but not yet made ready - THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated + THREADSTATUS_RUNNING, ///< Currently running + THREADSTATUS_READY, ///< Ready to run + THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter + THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC + THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false + THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true + THREADSTATUS_DORMANT, ///< Created but not yet made ready + THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated }; namespace Kernel { @@ -72,8 +73,8 @@ public: return HANDLE_TYPE; } - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; /** * Gets the thread's current priority @@ -90,6 +91,12 @@ public: void SetPriority(s32 priority); /** + * Boost's a thread's priority to the best priority among the thread's held mutexes. + * This prevents priority inversion via priority inheritance. + */ + void UpdatePriority(); + + /** * Temporarily boosts the thread's priority until the next time it is scheduled * @param priority The new priority */ @@ -128,13 +135,14 @@ public: /** * Retrieves the index that this particular object occupies in the list of objects - * that the thread passed to WaitSynchronizationN. + * that the thread passed to WaitSynchronizationN, starting the search from the last element. * It is used to set the output value of WaitSynchronizationN when the thread is awakened. + * When a thread wakes up due to an object signal, the kernel will use the index of the last + * matching object in the wait objects list in case of having multiple instances of the same + * object in the list. * @param object Object to query the index of. */ - s32 GetWaitObjectIndex(const WaitObject* object) const { - return wait_objects_index.at(object->GetObjectId()); - } + s32 GetWaitObjectIndex(WaitObject* object) const; /** * Stops a thread, invalidating it from further use @@ -152,10 +160,10 @@ public: /** * Returns whether this thread is waiting for all the objects in * its wait list to become ready, as a result of a WaitSynchronizationN call - * with wait_all = true, or a ReplyAndReceive call. + * with wait_all = true. */ bool IsSleepingOnWaitAll() const { - return !wait_objects.empty(); + return status == THREADSTATUS_WAIT_SYNCH_ALL; } ARM_Interface::ThreadContext context; @@ -178,15 +186,15 @@ public: /// Mutexes currently held by this thread, which will be released when it exits. boost::container::flat_set<SharedPtr<Mutex>> held_mutexes; + /// Mutexes that this thread is currently waiting for. + boost::container::flat_set<SharedPtr<Mutex>> pending_mutexes; + SharedPtr<Process> owner_process; ///< Process that owns this thread - /// Objects that the thread is waiting on. - /// This is only populated when the thread should wait for all the objects to become ready. + /// Objects that the thread is waiting on, in the same order as they were + // passed to WaitSynchronization1/N. std::vector<SharedPtr<WaitObject>> wait_objects; - /// Mapping of Object ids to their position in the last waitlist that this object waited on. - boost::container::flat_map<int, s32> wait_objects_index; - VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address /// True if the WaitSynchronizationN output parameter should be set on thread wakeup. @@ -211,6 +219,11 @@ private: SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority); /** + * Returns whether there are any threads that are ready to run. + */ +bool HaveReadyThreads(); + +/** * Reschedules to the next available thread (call after current thread is suspended) */ void Reschedule(); @@ -238,15 +251,6 @@ Thread* GetCurrentThread(); void WaitCurrentThread_Sleep(); /** - * Waits the current thread from a WaitSynchronization call - * @param wait_objects Kernel objects that we are waiting on - * @param wait_set_output If true, set the output parameter on thread wakeup (for - * WaitSynchronizationN only) - */ -void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects, - bool wait_set_output); - -/** * Waits the current thread from an ArbitrateAddress call * @param wait_address Arbitration address used to resume from wait */ diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index b50cf520d..8f2bc4c7f 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -39,12 +39,12 @@ SharedPtr<Timer> Timer::Create(ResetType reset_type, std::string name) { return timer; } -bool Timer::ShouldWait() { +bool Timer::ShouldWait(Thread* thread) const { return !signaled; } -void Timer::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Timer::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); if (reset_type == ResetType::OneShot) signaled = false; diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h index 18ea0236b..2e3b31b23 100644 --- a/src/core/hle/kernel/timer.h +++ b/src/core/hle/kernel/timer.h @@ -39,8 +39,8 @@ public: u64 initial_delay; ///< The delay until the timer fires for the first time u64 interval_delay; ///< The delay until the timer fires after the first time - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; /** * Starts the timer, with the specified initial delay and interval. diff --git a/src/core/hle/service/boss/boss.cpp b/src/core/hle/service/boss/boss.cpp index 6ab16ccd5..e0de037f8 100644 --- a/src/core/hle/service/boss/boss.cpp +++ b/src/core/hle/service/boss/boss.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> #include "core/hle/service/boss/boss.h" #include "core/hle/service/boss/boss_p.h" #include "core/hle/service/boss/boss_u.h" @@ -33,7 +34,8 @@ void InitializeSession(Service::Interface* self) { cmd_buff[0] = IPC::MakeHeader(0x1, 0x1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_BOSS, "(STUBBED) unk_param=0x%016X, translation=0x%08X, unk_param4=0x%08X", + LOG_WARNING(Service_BOSS, + "(STUBBED) unk_param=0x%016" PRIX64 ", translation=0x%08X, unk_param4=0x%08X", unk_param, translation, unk_param4); } diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 65655f45d..59dd6d1cd 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -84,7 +84,6 @@ struct ConsoleCountryInfo { static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exactly 4 bytes"); } -static const u64 CFG_SAVE_ID = 0x00010017; static const u64 CONSOLE_UNIQUE_ID = 0xDEADC0DE; static const ConsoleModelInfo CONSOLE_MODEL = {NINTENDO_3DS_XL, {0, 0, 0}}; static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN; @@ -115,6 +114,8 @@ static const std::vector<u8> cfg_system_savedata_id = { 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x01, 0x00, }; +static u32 preferred_region_code = 0; + void GetCountryCodeString(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 country_code_id = cmd_buff[1]; @@ -160,11 +161,18 @@ void GetCountryCodeID(Service::Interface* self) { cmd_buff[2] = country_code_id; } +static u32 GetRegionValue() { + if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) + return preferred_region_code; + + return Settings::values.region_value; +} + void SecureInfoGetRegion(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = Settings::values.region_value; + cmd_buff[2] = GetRegionValue(); } void GenHashConsoleUnique(Service::Interface* self) { @@ -184,7 +192,7 @@ void GetRegionCanadaUSA(Service::Interface* self) { cmd_buff[1] = RESULT_SUCCESS.raw; u8 canada_or_usa = 1; - if (canada_or_usa == Settings::values.region_value) { + if (canada_or_usa == GetRegionValue()) { cmd_buff[2] = 1; } else { cmd_buff[2] = 0; @@ -314,10 +322,47 @@ static ResultVal<void*> GetConfigInfoBlockPointer(u32 block_id, u32 size, u32 fl return MakeResult<void*>(pointer); } +/// Checks if the language is available in the chosen region, and returns a proper one +static u8 AdjustLanguageInfoBlock(u32 region, u8 language) { + static const std::array<std::vector<u8>, 7> region_languages{{ + // JPN + {LANGUAGE_JP}, + // USA + {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_ES, LANGUAGE_PT}, + // EUR + {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT, + LANGUAGE_RU}, + // AUS + {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT, + LANGUAGE_RU}, + // CHN + {LANGUAGE_ZH}, + // KOR + {LANGUAGE_KO}, + // TWN + {LANGUAGE_TW}, + }}; + const auto& available = region_languages[region]; + if (std::find(available.begin(), available.end(), language) == available.end()) { + return available[0]; + } + return language; +} + ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, void* output) { void* pointer; CASCADE_RESULT(pointer, GetConfigInfoBlockPointer(block_id, size, flag)); memcpy(output, pointer, size); + + // override the language setting if the region setting is auto + if (block_id == LanguageBlockID && + Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) { + u8 language; + memcpy(&language, output, sizeof(u8)); + language = AdjustLanguageInfoBlock(preferred_region_code, language); + memcpy(output, &language, sizeof(u8)); + } + return RESULT_SUCCESS; } @@ -535,10 +580,17 @@ void Init() { AddService(new CFG_U); LoadConfigNANDSaveFile(); + + preferred_region_code = 0; } void Shutdown() {} +void SetPreferredRegionCode(u32 region_code) { + preferred_region_code = region_code; + LOG_INFO(Service_CFG, "Preferred region code set to %u", preferred_region_code); +} + void SetUsername(const std::u16string& name) { ASSERT(name.size() <= 10); UsernameBlock block{}; diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index fb47c2aa5..618c9647e 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -282,6 +282,13 @@ void Init(); /// Shutdown the config service void Shutdown(); +/** + * Set the region code preferred by the game so that CFG will adjust to it when the region setting + * is auto. + * @param region_code the preferred region code to set + */ +void SetPreferredRegionCode(u32 region_code); + // Utilities for frontend to set config data. // Note: before calling these functions, LoadConfigNANDSaveFile should be called, // and UpdateConfigNANDSavegame should be called after making changes to config data. diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp index 4f1dd2fce..c62f8afc6 100644 --- a/src/core/hle/service/mic_u.cpp +++ b/src/core/hle/service/mic_u.cpp @@ -99,7 +99,8 @@ static void StartSampling(Interface* self) { is_sampling = true; LOG_WARNING(Service_MIC, "(STUBBED) called, encoding=%u, sample_rate=%u, " "audio_buffer_offset=%d, audio_buffer_size=%u, audio_buffer_loop=%u", - encoding, sample_rate, audio_buffer_offset, audio_buffer_size, audio_buffer_loop); + static_cast<u32>(encoding), static_cast<u32>(sample_rate), audio_buffer_offset, + audio_buffer_size, audio_buffer_loop); } /** @@ -114,7 +115,7 @@ static void AdjustSampling(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); sample_rate = static_cast<SampleRate>(cmd_buff[1] & 0xFF); cmd_buff[1] = RESULT_SUCCESS.raw; // No error - LOG_WARNING(Service_MIC, "(STUBBED) called, sample_rate=%u", sample_rate); + LOG_WARNING(Service_MIC, "(STUBBED) called, sample_rate=%u", static_cast<u32>(sample_rate)); } /** diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp index d9738c6a1..e248285f9 100644 --- a/src/core/hle/service/nfc/nfc.cpp +++ b/src/core/hle/service/nfc/nfc.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/kernel/event.h" #include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc_m.h" #include "core/hle/service/nfc/nfc_u.h" @@ -9,9 +10,28 @@ namespace Service { namespace NFC { +static Kernel::SharedPtr<Kernel::Event> tag_in_range_event; + +void GetTagInRangeEvent(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0xB, 1, 2); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = IPC::CopyHandleDesc(); + cmd_buff[3] = Kernel::g_handle_table.Create(tag_in_range_event).MoveFrom(); + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + void Init() { AddService(new NFC_M()); AddService(new NFC_U()); + + tag_in_range_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "NFC::tag_in_range_event"); +} + +void Shutdown() { + tag_in_range_event = nullptr; } } // namespace NFC diff --git a/src/core/hle/service/nfc/nfc.h b/src/core/hle/service/nfc/nfc.h index cd65a5fdc..b02354201 100644 --- a/src/core/hle/service/nfc/nfc.h +++ b/src/core/hle/service/nfc/nfc.h @@ -5,10 +5,27 @@ #pragma once namespace Service { + +class Interface; + namespace NFC { +/** + * NFC::GetTagInRangeEvent service function + * Inputs: + * 0 : Header code [0x000B0000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Copy handle descriptor + * 3 : Event Handle + */ +void GetTagInRangeEvent(Interface* self); + /// Initialize all NFC services. void Init(); +/// Shutdown all NFC services. +void Shutdown(); + } // namespace NFC } // namespace Service diff --git a/src/core/hle/service/nfc/nfc_m.cpp b/src/core/hle/service/nfc/nfc_m.cpp index 717335c11..f43b4029a 100644 --- a/src/core/hle/service/nfc/nfc_m.cpp +++ b/src/core/hle/service/nfc/nfc_m.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc_m.h" namespace Service { @@ -19,6 +20,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00070000, nullptr, "LoadAmiiboData"}, {0x00080000, nullptr, "ResetTagScanState"}, {0x00090002, nullptr, "UpdateStoredAmiiboData"}, + {0x000B0000, GetTagInRangeEvent, "GetTagInRangeEvent"}, {0x000D0000, nullptr, "GetTagState"}, {0x000F0000, nullptr, "CommunicationGetStatus"}, {0x00100000, nullptr, "GetTagInfo2"}, diff --git a/src/core/hle/service/nfc/nfc_u.cpp b/src/core/hle/service/nfc/nfc_u.cpp index deffb0b4f..4b5200ae8 100644 --- a/src/core/hle/service/nfc/nfc_u.cpp +++ b/src/core/hle/service/nfc/nfc_u.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc_u.h" namespace Service { @@ -18,6 +19,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00070000, nullptr, "LoadAmiiboData"}, {0x00080000, nullptr, "ResetTagScanState"}, {0x00090002, nullptr, "UpdateStoredAmiiboData"}, + {0x000B0000, GetTagInRangeEvent, "GetTagInRangeEvent"}, {0x000D0000, nullptr, "GetTagState"}, {0x000F0000, nullptr, "CommunicationGetStatus"}, {0x00100000, nullptr, "GetTagInfo2"}, diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 7e52a05d9..f3190e0fa 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -178,6 +178,7 @@ void Init() { /// Shutdown ServiceManager void Shutdown() { PTM::Shutdown(); + NFC::Shutdown(); NIM::Shutdown(); NEWS::Shutdown(); NDM::Shutdown(); diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp index c3918cdd0..dcc5c3c90 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -603,7 +603,6 @@ static void RecvFrom(Interface* self) { u32 socket_handle = cmd_buffer[1]; u32 len = cmd_buffer[2]; u32 flags = cmd_buffer[3]; - socklen_t addr_len = static_cast<socklen_t>(cmd_buffer[4]); struct { u32 output_buffer_descriptor; @@ -693,7 +692,6 @@ static void Poll(Interface* self) { static void GetSockName(Interface* self) { u32* cmd_buffer = Kernel::GetCommandBuffer(); u32 socket_handle = cmd_buffer[1]; - socklen_t ctr_len = cmd_buffer[2]; // Memory address of the ctr_dest_addr structure VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2]; @@ -734,7 +732,6 @@ static void Shutdown(Interface* self) { static void GetPeerName(Interface* self) { u32* cmd_buffer = Kernel::GetCommandBuffer(); u32 socket_handle = cmd_buffer[1]; - socklen_t len = cmd_buffer[2]; // Memory address of the ctr_dest_addr structure VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2]; @@ -765,7 +762,6 @@ static void Connect(Interface* self) { // performing nonblocking operations and spinlock until the data is available u32* cmd_buffer = Kernel::GetCommandBuffer(); u32 socket_handle = cmd_buffer[1]; - socklen_t len = cmd_buffer[2]; // Memory address of the ctr_input_addr structure VAddr ctr_input_addr_addr = cmd_buffer[6]; diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 2ca270de3..2b242ff98 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -248,6 +248,8 @@ static ResultCode SendSyncRequest(Kernel::Handle handle) { LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s)", handle, session->GetName().c_str()); + Core::System::GetInstance().PrepareReschedule(); + // TODO(Subv): svcSendSyncRequest should put the caller thread to sleep while the server // responds and cause a reschedule. return session->SendSyncRequest(); @@ -270,27 +272,27 @@ static ResultCode WaitSynchronization1(Kernel::Handle handle, s64 nano_seconds) LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s:%s), nanoseconds=%lld", handle, object->GetTypeName().c_str(), object->GetName().c_str(), nano_seconds); - if (object->ShouldWait()) { + if (object->ShouldWait(thread)) { if (nano_seconds == 0) return ERR_SYNC_TIMEOUT; + thread->wait_objects = {object}; object->AddWaitingThread(thread); - // TODO(Subv): Perform things like update the mutex lock owner's priority to - // prevent priority inversion. Currently this is done in Mutex::ShouldWait, - // but it should be moved to a function that is called from here. - thread->status = THREADSTATUS_WAIT_SYNCH; + thread->status = THREADSTATUS_WAIT_SYNCH_ANY; // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); + Core::System::GetInstance().PrepareReschedule(); + // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread // resumes due to a signal in its wait objects. // Otherwise we retain the default value of timeout. return ERR_SYNC_TIMEOUT; } - object->Acquire(); + object->Acquire(thread); return RESULT_SUCCESS; } @@ -324,19 +326,14 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha objects[i] = object; } - // Clear the mapping of wait object indices. - // We don't want any lingering state in this map. - // It will be repopulated later in the wait_all = false case. - thread->wait_objects_index.clear(); - if (wait_all) { bool all_available = std::all_of(objects.begin(), objects.end(), - [](const ObjectPtr& object) { return !object->ShouldWait(); }); + [thread](const ObjectPtr& object) { return !object->ShouldWait(thread); }); if (all_available) { // We can acquire all objects right now, do so. for (auto& object : objects) - object->Acquire(); + object->Acquire(thread); // Note: In this case, the `out` parameter is not set, // and retains whatever value it had before. return RESULT_SUCCESS; @@ -350,22 +347,20 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha return ERR_SYNC_TIMEOUT; // Put the thread to sleep - thread->status = THREADSTATUS_WAIT_SYNCH; + thread->status = THREADSTATUS_WAIT_SYNCH_ALL; // Add the thread to each of the objects' waiting threads. for (auto& object : objects) { object->AddWaitingThread(thread); - // TODO(Subv): Perform things like update the mutex lock owner's priority to - // prevent priority inversion. Currently this is done in Mutex::ShouldWait, - // but it should be moved to a function that is called from here. } - // Set the thread's waitlist to the list of objects passed to WaitSynchronizationN thread->wait_objects = std::move(objects); // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); + Core::System::GetInstance().PrepareReschedule(); + // This value gets set to -1 by default in this case, it is not modified after this. *out = -1; // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to @@ -373,13 +368,14 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha return ERR_SYNC_TIMEOUT; } else { // Find the first object that is acquirable in the provided list of objects - auto itr = std::find_if(objects.begin(), objects.end(), - [](const ObjectPtr& object) { return !object->ShouldWait(); }); + auto itr = std::find_if(objects.begin(), objects.end(), [thread](const ObjectPtr& object) { + return !object->ShouldWait(thread); + }); if (itr != objects.end()) { // We found a ready object, acquire it and set the result value Kernel::WaitObject* object = itr->get(); - object->Acquire(); + object->Acquire(thread); *out = std::distance(objects.begin(), itr); return RESULT_SUCCESS; } @@ -392,28 +388,24 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha return ERR_SYNC_TIMEOUT; // Put the thread to sleep - thread->status = THREADSTATUS_WAIT_SYNCH; - - // Clear the thread's waitlist, we won't use it for wait_all = false - thread->wait_objects.clear(); + thread->status = THREADSTATUS_WAIT_SYNCH_ANY; // Add the thread to each of the objects' waiting threads. for (size_t i = 0; i < objects.size(); ++i) { Kernel::WaitObject* object = objects[i].get(); - // Set the index of this object in the mapping of Objects -> index for this thread. - thread->wait_objects_index[object->GetObjectId()] = static_cast<int>(i); object->AddWaitingThread(thread); - // TODO(Subv): Perform things like update the mutex lock owner's priority to - // prevent priority inversion. Currently this is done in Mutex::ShouldWait, - // but it should be moved to a function that is called from here. } + thread->wait_objects = std::move(objects); + // Note: If no handles and no timeout were given, then the thread will deadlock, this is // consistent with hardware behavior. // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); + Core::System::GetInstance().PrepareReschedule(); + // 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. // Otherwise we retain the default value of timeout, and -1 in the out parameter @@ -448,6 +440,9 @@ static ResultCode ArbitrateAddress(Kernel::Handle handle, u32 address, u32 type, auto res = arbiter->ArbitrateAddress(static_cast<Kernel::ArbitrationType>(type), address, value, nanoseconds); + // TODO(Subv): Identify in which specific cases this call should cause a reschedule. + Core::System::GetInstance().PrepareReschedule(); + return res; } @@ -574,6 +569,8 @@ static ResultCode CreateThread(Kernel::Handle* out_handle, s32 priority, u32 ent CASCADE_RESULT(*out_handle, Kernel::g_handle_table.Create(std::move(thread))); + Core::System::GetInstance().PrepareReschedule(); + LOG_TRACE(Kernel_SVC, "called entrypoint=0x%08X (%s), arg=0x%08X, stacktop=0x%08X, " "threadpriority=0x%08X, processorid=0x%08X : created handle=0x%08X", entry_point, name.c_str(), arg, stack_top, priority, processor_id, *out_handle); @@ -586,6 +583,7 @@ static void ExitThread() { LOG_TRACE(Kernel_SVC, "called, pc=0x%08X", Core::CPU().GetPC()); Kernel::ExitCurrentThread(); + Core::System::GetInstance().PrepareReschedule(); } /// Gets the priority for the specified thread @@ -605,6 +603,13 @@ static ResultCode SetThreadPriority(Kernel::Handle handle, s32 priority) { return ERR_INVALID_HANDLE; thread->SetPriority(priority); + thread->UpdatePriority(); + + // Update the mutexes that this thread is waiting for + for (auto& mutex : thread->pending_mutexes) + mutex->UpdatePriority(); + + Core::System::GetInstance().PrepareReschedule(); return RESULT_SUCCESS; } @@ -844,11 +849,18 @@ static ResultCode CancelTimer(Kernel::Handle handle) { static void SleepThread(s64 nanoseconds) { LOG_TRACE(Kernel_SVC, "called nanoseconds=%lld", nanoseconds); + // Don't attempt to yield execution if there are no available threads to run, + // this way we avoid a useless reschedule to the idle thread. + if (nanoseconds == 0 && !Kernel::HaveReadyThreads()) + return; + // Sleep current thread and check for next thread to schedule Kernel::WaitCurrentThread_Sleep(); // Create an event to wake the thread up after the specified nanosecond delay has passed Kernel::GetCurrentThread()->WakeAfterDelay(nanoseconds); + + Core::System::GetInstance().PrepareReschedule(); } /// This returns the total CPU ticks elapsed since the CPU was powered-on @@ -890,7 +902,11 @@ static ResultCode CreateMemoryBlock(Kernel::Handle* out_handle, u32 addr, u32 si return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); - if (addr < Memory::PROCESS_IMAGE_VADDR || addr + size > Memory::SHARED_MEMORY_VADDR_END) { + // TODO(Subv): Processes with memory type APPLICATION are not allowed + // to create memory blocks with addr = 0, any attempts to do so + // should return error 0xD92007EA. + if ((addr < Memory::PROCESS_IMAGE_VADDR || addr + size > Memory::SHARED_MEMORY_VADDR_END) && + addr != 0) { return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); } @@ -1184,8 +1200,6 @@ void CallSVC(u32 immediate) { if (info) { if (info->func) { info->func(); - // TODO(Subv): Not all service functions should cause a reschedule in all cases. - Core::System::GetInstance().PrepareReschedule(); } else { LOG_ERROR(Kernel_SVC, "unimplemented SVC function %s(..)", info->name); } diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 6f2164428..5df33f6d2 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -11,8 +11,10 @@ #include "core/file_sys/archive_romfs.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" +#include "core/hle/service/cfg/cfg.h" #include "core/hle/service/fs/archive.h" #include "core/loader/ncch.h" +#include "core/loader/smdh.h" #include "core/memory.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -286,7 +288,7 @@ ResultStatus AppLoader_NCCH::LoadExeFS() { LOG_DEBUG(Loader, "Thread priority: 0x%X", priority); LOG_DEBUG(Loader, "Resource limit category: %d", resource_limit_category); LOG_DEBUG(Loader, "System Mode: %d", - exheader_header.arm11_system_local_caps.system_mode); + static_cast<int>(exheader_header.arm11_system_local_caps.system_mode)); if (exheader_header.arm11_system_local_caps.program_id != ncch_header.program_id) { LOG_ERROR(Loader, "ExHeader Program ID mismatch: the ROM is probably encrypted."); @@ -309,6 +311,23 @@ ResultStatus AppLoader_NCCH::LoadExeFS() { return ResultStatus::Success; } +void AppLoader_NCCH::ParseRegionLockoutInfo() { + std::vector<u8> smdh_buffer; + if (ReadIcon(smdh_buffer) == ResultStatus::Success && smdh_buffer.size() >= sizeof(SMDH)) { + SMDH smdh; + memcpy(&smdh, smdh_buffer.data(), sizeof(SMDH)); + u32 region_lockout = smdh.region_lockout; + constexpr u32 REGION_COUNT = 7; + for (u32 region = 0; region < REGION_COUNT; ++region) { + if (region_lockout & 1) { + Service::CFG::SetPreferredRegionCode(region); + break; + } + region_lockout >>= 1; + } + } +} + ResultStatus AppLoader_NCCH::Load() { if (is_loaded) return ResultStatus::ErrorAlreadyLoaded; @@ -325,6 +344,9 @@ ResultStatus AppLoader_NCCH::Load() { Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this), Service::FS::ArchiveIdCode::RomFS); + + ParseRegionLockoutInfo(); + return ResultStatus::Success; } diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index 6afc171a5..4ef95b5c6 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -181,7 +181,7 @@ public: * Loads the Exheader and returns the system mode for this application. * @return Optional with the kernel system mode */ - boost::optional<u32> LoadKernelSystemMode(); + boost::optional<u32> LoadKernelSystemMode() override; ResultStatus ReadCode(std::vector<u8>& buffer) override; @@ -229,6 +229,9 @@ private: */ ResultStatus LoadExeFS(); + /// Reads the region lockout info in the SMDH and send it to CFG service + void ParseRegionLockoutInfo(); + bool is_exefs_loaded = false; bool is_compressed = false; diff --git a/src/core/settings.h b/src/core/settings.h index db4c8fada..4e7a4b1be 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -110,5 +110,9 @@ struct Values { u16 gdbstub_port; } extern values; +// a special value for Values::region_value indicating that citra will automatically select a region +// value to fit the region lockout info of the game +static constexpr int REGION_VALUE_AUTO_SELECT = -1; + void Apply(); } |