mirror of
				https://git.suyu.dev/suyu/suyu
				synced 2025-11-04 00:49:02 -06:00 
			
		
		
		
	Merge pull request #9666 from liamwhite/wait-for-me
kernel: fix incorrect locking order in suspension
This commit is contained in:
		@@ -171,7 +171,7 @@ Result KConditionVariable::WaitForAddress(Handle handle, VAddr addr, u32 value)
 | 
			
		||||
        R_UNLESS(owner_thread != nullptr, ResultInvalidHandle);
 | 
			
		||||
 | 
			
		||||
        // Update the lock.
 | 
			
		||||
        cur_thread->SetAddressKey(addr, value);
 | 
			
		||||
        cur_thread->SetUserAddressKey(addr, value);
 | 
			
		||||
        owner_thread->AddWaiter(cur_thread);
 | 
			
		||||
 | 
			
		||||
        // Begin waiting.
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ bool KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
 | 
			
		||||
 | 
			
		||||
        // Add the current thread as a waiter on the owner.
 | 
			
		||||
        KThread* owner_thread = reinterpret_cast<KThread*>(_owner & ~1ULL);
 | 
			
		||||
        cur_thread->SetAddressKey(reinterpret_cast<uintptr_t>(std::addressof(tag)));
 | 
			
		||||
        cur_thread->SetKernelAddressKey(reinterpret_cast<uintptr_t>(std::addressof(tag)));
 | 
			
		||||
        owner_thread->AddWaiter(cur_thread);
 | 
			
		||||
 | 
			
		||||
        // Begin waiting to hold the lock.
 | 
			
		||||
 
 | 
			
		||||
@@ -67,9 +67,9 @@ constexpr size_t KernelPageBufferAdditionalSize = 0x33C000;
 | 
			
		||||
constexpr std::size_t KernelResourceSize = KernelPageTableHeapSize + KernelInitialPageHeapSize +
 | 
			
		||||
                                           KernelSlabHeapSize + KernelPageBufferHeapSize;
 | 
			
		||||
 | 
			
		||||
constexpr bool IsKernelAddressKey(VAddr key) {
 | 
			
		||||
    return KernelVirtualAddressSpaceBase <= key && key <= KernelVirtualAddressSpaceLast;
 | 
			
		||||
}
 | 
			
		||||
//! NB: Use KThread::GetAddressKeyIsKernel().
 | 
			
		||||
//! See explanation for deviation of GetAddressKey.
 | 
			
		||||
bool IsKernelAddressKey(VAddr key) = delete;
 | 
			
		||||
 | 
			
		||||
constexpr bool IsKernelAddress(VAddr address) {
 | 
			
		||||
    return KernelVirtualAddressSpaceBase <= address && address < KernelVirtualAddressSpaceEnd;
 | 
			
		||||
 
 | 
			
		||||
@@ -330,7 +330,7 @@ void KThread::Finalize() {
 | 
			
		||||
            KThread* const waiter = std::addressof(*it);
 | 
			
		||||
 | 
			
		||||
            // The thread shouldn't be a kernel waiter.
 | 
			
		||||
            ASSERT(!IsKernelAddressKey(waiter->GetAddressKey()));
 | 
			
		||||
            ASSERT(!waiter->GetAddressKeyIsKernel());
 | 
			
		||||
 | 
			
		||||
            // Clear the lock owner.
 | 
			
		||||
            waiter->SetLockOwner(nullptr);
 | 
			
		||||
@@ -763,19 +763,6 @@ void KThread::Continue() {
 | 
			
		||||
    KScheduler::OnThreadStateChanged(kernel, this, old_state);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void KThread::WaitUntilSuspended() {
 | 
			
		||||
    // Make sure we have a suspend requested.
 | 
			
		||||
    ASSERT(IsSuspendRequested());
 | 
			
		||||
 | 
			
		||||
    // Loop until the thread is not executing on any core.
 | 
			
		||||
    for (std::size_t i = 0; i < static_cast<std::size_t>(Core::Hardware::NUM_CPU_CORES); ++i) {
 | 
			
		||||
        KThread* core_thread{};
 | 
			
		||||
        do {
 | 
			
		||||
            core_thread = kernel.Scheduler(i).GetSchedulerCurrentThread();
 | 
			
		||||
        } while (core_thread == this);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Result KThread::SetActivity(Svc::ThreadActivity activity) {
 | 
			
		||||
    // Lock ourselves.
 | 
			
		||||
    KScopedLightLock lk(activity_pause_lock);
 | 
			
		||||
@@ -897,7 +884,7 @@ void KThread::AddWaiterImpl(KThread* thread) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Keep track of how many kernel waiters we have.
 | 
			
		||||
    if (IsKernelAddressKey(thread->GetAddressKey())) {
 | 
			
		||||
    if (thread->GetAddressKeyIsKernel()) {
 | 
			
		||||
        ASSERT((num_kernel_waiters++) >= 0);
 | 
			
		||||
        KScheduler::SetSchedulerUpdateNeeded(kernel);
 | 
			
		||||
    }
 | 
			
		||||
@@ -911,7 +898,7 @@ void KThread::RemoveWaiterImpl(KThread* thread) {
 | 
			
		||||
    ASSERT(kernel.GlobalSchedulerContext().IsLocked());
 | 
			
		||||
 | 
			
		||||
    // Keep track of how many kernel waiters we have.
 | 
			
		||||
    if (IsKernelAddressKey(thread->GetAddressKey())) {
 | 
			
		||||
    if (thread->GetAddressKeyIsKernel()) {
 | 
			
		||||
        ASSERT((num_kernel_waiters--) > 0);
 | 
			
		||||
        KScheduler::SetSchedulerUpdateNeeded(kernel);
 | 
			
		||||
    }
 | 
			
		||||
@@ -987,7 +974,7 @@ KThread* KThread::RemoveWaiterByKey(s32* out_num_waiters, VAddr key) {
 | 
			
		||||
            KThread* thread = std::addressof(*it);
 | 
			
		||||
 | 
			
		||||
            // Keep track of how many kernel waiters we have.
 | 
			
		||||
            if (IsKernelAddressKey(thread->GetAddressKey())) {
 | 
			
		||||
            if (thread->GetAddressKeyIsKernel()) {
 | 
			
		||||
                ASSERT((num_kernel_waiters--) > 0);
 | 
			
		||||
                KScheduler::SetSchedulerUpdateNeeded(kernel);
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -214,8 +214,6 @@ public:
 | 
			
		||||
 | 
			
		||||
    void Continue();
 | 
			
		||||
 | 
			
		||||
    void WaitUntilSuspended();
 | 
			
		||||
 | 
			
		||||
    constexpr void SetSyncedIndex(s32 index) {
 | 
			
		||||
        synced_index = index;
 | 
			
		||||
    }
 | 
			
		||||
@@ -607,13 +605,30 @@ public:
 | 
			
		||||
        return address_key_value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void SetAddressKey(VAddr key) {
 | 
			
		||||
        address_key = key;
 | 
			
		||||
    [[nodiscard]] bool GetAddressKeyIsKernel() const {
 | 
			
		||||
        return address_key_is_kernel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void SetAddressKey(VAddr key, u32 val) {
 | 
			
		||||
    //! NB: intentional deviation from official kernel.
 | 
			
		||||
    //
 | 
			
		||||
    // Separate SetAddressKey into user and kernel versions
 | 
			
		||||
    // to cope with arbitrary host pointers making their way
 | 
			
		||||
    // into things.
 | 
			
		||||
 | 
			
		||||
    void SetUserAddressKey(VAddr key) {
 | 
			
		||||
        address_key = key;
 | 
			
		||||
        address_key_is_kernel = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void SetUserAddressKey(VAddr key, u32 val) {
 | 
			
		||||
        address_key = key;
 | 
			
		||||
        address_key_value = val;
 | 
			
		||||
        address_key_is_kernel = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void SetKernelAddressKey(VAddr key) {
 | 
			
		||||
        address_key = key;
 | 
			
		||||
        address_key_is_kernel = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ClearWaitQueue() {
 | 
			
		||||
@@ -772,6 +787,7 @@ private:
 | 
			
		||||
    bool debug_attached{};
 | 
			
		||||
    s8 priority_inheritance_count{};
 | 
			
		||||
    bool resource_limit_release_hint{};
 | 
			
		||||
    bool address_key_is_kernel{};
 | 
			
		||||
    StackParameters stack_parameters{};
 | 
			
		||||
    Common::SpinLock context_guard{};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1198,27 +1198,34 @@ void KernelCore::Suspend(bool suspended) {
 | 
			
		||||
    const bool should_suspend{exception_exited || suspended};
 | 
			
		||||
    const auto activity = should_suspend ? ProcessActivity::Paused : ProcessActivity::Runnable;
 | 
			
		||||
 | 
			
		||||
    std::vector<KScopedAutoObject<KThread>> process_threads;
 | 
			
		||||
    {
 | 
			
		||||
        KScopedSchedulerLock sl{*this};
 | 
			
		||||
 | 
			
		||||
        if (auto* process = CurrentProcess(); process != nullptr) {
 | 
			
		||||
            process->SetActivity(activity);
 | 
			
		||||
 | 
			
		||||
            if (!should_suspend) {
 | 
			
		||||
                // Runnable now; no need to wait.
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (auto* thread : process->GetThreadList()) {
 | 
			
		||||
                process_threads.emplace_back(thread);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    //! This refers to the application process, not the current process.
 | 
			
		||||
    KScopedAutoObject<KProcess> process = CurrentProcess();
 | 
			
		||||
    if (process.IsNull()) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Wait for execution to stop.
 | 
			
		||||
    for (auto& thread : process_threads) {
 | 
			
		||||
        thread->WaitUntilSuspended();
 | 
			
		||||
    // Set the new activity.
 | 
			
		||||
    process->SetActivity(activity);
 | 
			
		||||
 | 
			
		||||
    // Wait for process execution to stop.
 | 
			
		||||
    bool must_wait{should_suspend};
 | 
			
		||||
 | 
			
		||||
    // KernelCore::Suspend must be called from locked context, or we
 | 
			
		||||
    // could race another call to SetActivity, interfering with waiting.
 | 
			
		||||
    while (must_wait) {
 | 
			
		||||
        KScopedSchedulerLock sl{*this};
 | 
			
		||||
 | 
			
		||||
        // Assume that all threads have finished running.
 | 
			
		||||
        must_wait = false;
 | 
			
		||||
 | 
			
		||||
        for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) {
 | 
			
		||||
            if (Scheduler(i).GetSchedulerCurrentThread()->GetOwnerProcess() ==
 | 
			
		||||
                process.GetPointerUnsafe()) {
 | 
			
		||||
                // A thread has not finished running yet.
 | 
			
		||||
                // Continue waiting.
 | 
			
		||||
                must_wait = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user