core: hle: kernel: Implement thread pinning.

- We largely had the mechanics in place for thread pinning, this change hooks these up.
- Validated with tests https://github.com/Atmosphere-NX/Atmosphere/blob/master/tests/TestSvc/source/test_thread_pinning.cpp.
This commit is contained in:
bunnei 2021-12-29 21:40:38 -08:00
parent 5e58271903
commit 3a89723d97
10 changed files with 140 additions and 14 deletions

View file

@ -187,6 +187,8 @@ add_library(core STATIC
hle/kernel/k_event.h hle/kernel/k_event.h
hle/kernel/k_handle_table.cpp hle/kernel/k_handle_table.cpp
hle/kernel/k_handle_table.h hle/kernel/k_handle_table.h
hle/kernel/k_interrupt_manager.cpp
hle/kernel/k_interrupt_manager.h
hle/kernel/k_light_condition_variable.cpp hle/kernel/k_light_condition_variable.cpp
hle/kernel/k_light_condition_variable.h hle/kernel/k_light_condition_variable.h
hle/kernel/k_light_lock.cpp hle/kernel/k_light_lock.cpp

View file

@ -9,6 +9,7 @@
#include "core/hle/kernel/global_scheduler_context.h" #include "core/hle/kernel/global_scheduler_context.h"
#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
namespace Kernel { namespace Kernel {
@ -42,6 +43,11 @@ void GlobalSchedulerContext::PreemptThreads() {
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
const u32 priority = preemption_priorities[core_id]; const u32 priority = preemption_priorities[core_id];
kernel.Scheduler(core_id).RotateScheduledQueue(core_id, priority); kernel.Scheduler(core_id).RotateScheduledQueue(core_id, priority);
// Signal an interrupt occurred. For core 3, this is a certainty, as preemption will result
// in the rotator thread being scheduled. For cores 0-2, this is to simulate or system
// interrupts that may have occurred.
kernel.PhysicalCore(core_id).Interrupt();
} }
} }

View file

@ -0,0 +1,34 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/kernel/k_interrupt_manager.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
namespace Kernel::KInterruptManager {
void HandleInterrupt(KernelCore& kernel, s32 core_id) {
auto* process = kernel.CurrentProcess();
if (!process) {
return;
}
auto& scheduler = kernel.Scheduler(core_id);
auto& current_thread = *scheduler.GetCurrentThread();
// If the user disable count is set, we may need to pin the current thread.
if (current_thread.GetUserDisableCount() && !process->GetPinnedThread(core_id)) {
KScopedSchedulerLock sl{kernel};
// Pin the current thread.
process->PinCurrentThread(core_id);
// Set the interrupt flag for the thread.
scheduler.GetCurrentThread()->SetInterruptFlag();
}
}
} // namespace Kernel::KInterruptManager

View file

@ -0,0 +1,17 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_types.h"
namespace Kernel {
class KernelCore;
namespace KInterruptManager {
void HandleInterrupt(KernelCore& kernel, s32 core_id);
}
} // namespace Kernel

View file

@ -220,30 +220,28 @@ bool KProcess::ReleaseUserException(KThread* thread) {
} }
} }
void KProcess::PinCurrentThread() { void KProcess::PinCurrentThread(s32 core_id) {
ASSERT(kernel.GlobalSchedulerContext().IsLocked()); ASSERT(kernel.GlobalSchedulerContext().IsLocked());
// Get the current thread. // Get the current thread.
const s32 core_id = GetCurrentCoreId(kernel); KThread* cur_thread = kernel.Scheduler(static_cast<std::size_t>(core_id)).GetCurrentThread();
KThread* cur_thread = GetCurrentThreadPointer(kernel);
// If the thread isn't terminated, pin it. // If the thread isn't terminated, pin it.
if (!cur_thread->IsTerminationRequested()) { if (!cur_thread->IsTerminationRequested()) {
// Pin it. // Pin it.
PinThread(core_id, cur_thread); PinThread(core_id, cur_thread);
cur_thread->Pin(); cur_thread->Pin(core_id);
// An update is needed. // An update is needed.
KScheduler::SetSchedulerUpdateNeeded(kernel); KScheduler::SetSchedulerUpdateNeeded(kernel);
} }
} }
void KProcess::UnpinCurrentThread() { void KProcess::UnpinCurrentThread(s32 core_id) {
ASSERT(kernel.GlobalSchedulerContext().IsLocked()); ASSERT(kernel.GlobalSchedulerContext().IsLocked());
// Get the current thread. // Get the current thread.
const s32 core_id = GetCurrentCoreId(kernel); KThread* cur_thread = kernel.Scheduler(static_cast<std::size_t>(core_id)).GetCurrentThread();
KThread* cur_thread = GetCurrentThreadPointer(kernel);
// Unpin it. // Unpin it.
cur_thread->Unpin(); cur_thread->Unpin();

View file

@ -345,8 +345,8 @@ public:
bool IsSignaled() const override; bool IsSignaled() const override;
void PinCurrentThread(); void PinCurrentThread(s32 core_id);
void UnpinCurrentThread(); void UnpinCurrentThread(s32 core_id);
void UnpinThread(KThread* thread); void UnpinThread(KThread* thread);
KLightLock& GetStateLock() { KLightLock& GetStateLock() {

View file

@ -15,6 +15,7 @@
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/cpu_manager.h" #include "core/cpu_manager.h"
#include "core/hle/kernel/k_interrupt_manager.h"
#include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" #include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
@ -53,6 +54,13 @@ void KScheduler::RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedul
} }
cores_pending_reschedule &= ~(1ULL << core); cores_pending_reschedule &= ~(1ULL << core);
} }
for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; ++core_id) {
if (kernel.PhysicalCore(core_id).IsInterrupted()) {
KInterruptManager::HandleInterrupt(kernel, static_cast<s32>(core_id));
}
}
if (must_context_switch) { if (must_context_switch) {
auto core_scheduler = kernel.CurrentScheduler(); auto core_scheduler = kernel.CurrentScheduler();
kernel.ExitSVCProfile(); kernel.ExitSVCProfile();

View file

@ -3,6 +3,7 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <algorithm> #include <algorithm>
#include <atomic>
#include <cinttypes> #include <cinttypes>
#include <optional> #include <optional>
#include <vector> #include <vector>
@ -33,6 +34,7 @@
#include "core/hle/kernel/svc_results.h" #include "core/hle/kernel/svc_results.h"
#include "core/hle/kernel/time_manager.h" #include "core/hle/kernel/time_manager.h"
#include "core/hle/result.h" #include "core/hle/result.h"
#include "core/memory.h"
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
#include "core/arm/dynarmic/arm_dynarmic_32.h" #include "core/arm/dynarmic/arm_dynarmic_32.h"
@ -63,6 +65,13 @@ namespace Kernel {
namespace { namespace {
struct ThreadLocalRegion {
static constexpr std::size_t MessageBufferSize = 0x100;
std::array<u32, MessageBufferSize / sizeof(u32)> message_buffer;
std::atomic_uint16_t disable_count;
std::atomic_uint16_t interrupt_flag;
};
class ThreadQueueImplForKThreadSleep final : public KThreadQueueWithoutEndWait { class ThreadQueueImplForKThreadSleep final : public KThreadQueueWithoutEndWait {
public: public:
explicit ThreadQueueImplForKThreadSleep(KernelCore& kernel_) explicit ThreadQueueImplForKThreadSleep(KernelCore& kernel_)
@ -346,7 +355,7 @@ void KThread::StartTermination() {
if (parent != nullptr) { if (parent != nullptr) {
parent->ReleaseUserException(this); parent->ReleaseUserException(this);
if (parent->GetPinnedThread(GetCurrentCoreId(kernel)) == this) { if (parent->GetPinnedThread(GetCurrentCoreId(kernel)) == this) {
parent->UnpinCurrentThread(); parent->UnpinCurrentThread(core_id);
} }
} }
@ -372,7 +381,7 @@ void KThread::StartTermination() {
this->Close(); this->Close();
} }
void KThread::Pin() { void KThread::Pin(s32 current_core) {
ASSERT(kernel.GlobalSchedulerContext().IsLocked()); ASSERT(kernel.GlobalSchedulerContext().IsLocked());
// Set ourselves as pinned. // Set ourselves as pinned.
@ -389,7 +398,6 @@ void KThread::Pin() {
// Bind ourselves to this core. // Bind ourselves to this core.
const s32 active_core = GetActiveCore(); const s32 active_core = GetActiveCore();
const s32 current_core = GetCurrentCoreId(kernel);
SetActiveCore(current_core); SetActiveCore(current_core);
physical_ideal_core_id = current_core; physical_ideal_core_id = current_core;
@ -482,6 +490,36 @@ void KThread::Unpin() {
} }
} }
u16 KThread::GetUserDisableCount() const {
if (!IsUserThread()) {
// We only emulate TLS for user threads
return {};
}
auto& memory = kernel.System().Memory();
return memory.Read16(tls_address + offsetof(ThreadLocalRegion, disable_count));
}
void KThread::SetInterruptFlag() {
if (!IsUserThread()) {
// We only emulate TLS for user threads
return;
}
auto& memory = kernel.System().Memory();
memory.Write16(tls_address + offsetof(ThreadLocalRegion, interrupt_flag), 1);
}
void KThread::ClearInterruptFlag() {
if (!IsUserThread()) {
// We only emulate TLS for user threads
return;
}
auto& memory = kernel.System().Memory();
memory.Write16(tls_address + offsetof(ThreadLocalRegion, interrupt_flag), 0);
}
ResultCode KThread::GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask) { ResultCode KThread::GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask) {
KScopedSchedulerLock sl{kernel}; KScopedSchedulerLock sl{kernel};

View file

@ -307,6 +307,10 @@ public:
return parent != nullptr; return parent != nullptr;
} }
u16 GetUserDisableCount() const;
void SetInterruptFlag();
void ClearInterruptFlag();
[[nodiscard]] KThread* GetLockOwner() const { [[nodiscard]] KThread* GetLockOwner() const {
return lock_owner; return lock_owner;
} }
@ -490,7 +494,7 @@ public:
this->GetStackParameters().disable_count--; this->GetStackParameters().disable_count--;
} }
void Pin(); void Pin(s32 current_core);
void Unpin(); void Unpin();

View file

@ -2027,6 +2027,25 @@ static ResultCode SignalToAddress(Core::System& system, VAddr address, Svc::Sign
count); count);
} }
static void SynchronizePreemptionState(Core::System& system) {
auto& kernel = system.Kernel();
// Lock the scheduler.
KScopedSchedulerLock sl{kernel};
// If the current thread is pinned, unpin it.
KProcess* cur_process = system.Kernel().CurrentProcess();
const auto core_id = GetCurrentCoreId(kernel);
if (cur_process->GetPinnedThread(core_id) == GetCurrentThreadPointer(kernel)) {
// Clear the current thread's interrupt flag.
GetCurrentThread(kernel).ClearInterruptFlag();
// Unpin the current thread.
cur_process->UnpinCurrentThread(core_id);
}
}
static ResultCode SignalToAddress32(Core::System& system, u32 address, Svc::SignalType signal_type, static ResultCode SignalToAddress32(Core::System& system, u32 address, Svc::SignalType signal_type,
s32 value, s32 count) { s32 value, s32 count) {
return SignalToAddress(system, address, signal_type, value, count); return SignalToAddress(system, address, signal_type, value, count);
@ -2797,7 +2816,7 @@ static const FunctionDef SVC_Table_64[] = {
{0x33, SvcWrap64<GetThreadContext>, "GetThreadContext"}, {0x33, SvcWrap64<GetThreadContext>, "GetThreadContext"},
{0x34, SvcWrap64<WaitForAddress>, "WaitForAddress"}, {0x34, SvcWrap64<WaitForAddress>, "WaitForAddress"},
{0x35, SvcWrap64<SignalToAddress>, "SignalToAddress"}, {0x35, SvcWrap64<SignalToAddress>, "SignalToAddress"},
{0x36, nullptr, "SynchronizePreemptionState"}, {0x36, SvcWrap64<SynchronizePreemptionState>, "SynchronizePreemptionState"},
{0x37, nullptr, "Unknown"}, {0x37, nullptr, "Unknown"},
{0x38, nullptr, "Unknown"}, {0x38, nullptr, "Unknown"},
{0x39, nullptr, "Unknown"}, {0x39, nullptr, "Unknown"},