yuzu/src/core/hle/kernel/physical_core.cpp

244 lines
7.1 KiB
C++

// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/debugger/debugger.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/svc.h"
namespace Kernel {
PhysicalCore::PhysicalCore(KernelCore& kernel, std::size_t core_index)
: m_kernel{kernel}, m_core_index{core_index} {
m_is_single_core = !kernel.IsMulticore();
}
PhysicalCore::~PhysicalCore() = default;
void PhysicalCore::RunThread(Kernel::KThread* thread) {
auto* process = thread->GetOwnerProcess();
auto& system = m_kernel.System();
auto* interface = process->GetArmInterface(m_core_index);
interface->Initialize();
const auto EnterContext = [&]() {
system.EnterCPUProfile();
// Lock the core context.
std::scoped_lock lk{m_guard};
// Check if we are already interrupted. If we are, we can just stop immediately.
if (m_is_interrupted) {
return false;
}
// Mark that we are running.
m_arm_interface = interface;
m_current_thread = thread;
// Acquire the lock on the thread parameters.
// This allows us to force synchronization with Interrupt.
interface->LockThread(thread);
return true;
};
const auto ExitContext = [&]() {
// Unlock the thread.
interface->UnlockThread(thread);
// Lock the core context.
std::scoped_lock lk{m_guard};
// On exit, we no longer are running.
m_arm_interface = nullptr;
m_current_thread = nullptr;
system.ExitCPUProfile();
};
while (true) {
// If the thread is scheduled for termination, exit.
if (thread->HasDpc() && thread->IsTerminationRequested()) {
thread->Exit();
}
// Notify the debugger and go to sleep if a step was performed
// and this thread has been scheduled again.
if (thread->GetStepState() == StepState::StepPerformed) {
system.GetDebugger().NotifyThreadStopped(thread);
thread->RequestSuspend(SuspendType::Debug);
return;
}
// Otherwise, run the thread.
Core::HaltReason hr{};
{
// If we were interrupted, exit immediately.
if (!EnterContext()) {
return;
}
if (thread->GetStepState() == StepState::StepPending) {
hr = interface->StepThread(thread);
if (True(hr & Core::HaltReason::StepThread)) {
thread->SetStepState(StepState::StepPerformed);
}
} else {
hr = interface->RunThread(thread);
}
ExitContext();
}
// Determine why we stopped.
const bool supervisor_call = True(hr & Core::HaltReason::SupervisorCall);
const bool prefetch_abort = True(hr & Core::HaltReason::PrefetchAbort);
const bool breakpoint = True(hr & Core::HaltReason::InstructionBreakpoint);
const bool data_abort = True(hr & Core::HaltReason::DataAbort);
const bool interrupt = True(hr & Core::HaltReason::BreakLoop);
// Since scheduling may occur here, we cannot use any cached
// state after returning from calls we make.
// Notify the debugger and go to sleep if a breakpoint was hit,
// or if the thread is unable to continue for any reason.
if (breakpoint || prefetch_abort) {
if (breakpoint) {
interface->RewindBreakpointInstruction();
}
if (system.DebuggerEnabled()) {
system.GetDebugger().NotifyThreadStopped(thread);
} else {
interface->LogBacktrace(process);
}
thread->RequestSuspend(SuspendType::Debug);
return;
}
// Notify the debugger and go to sleep on data abort.
if (data_abort) {
if (system.DebuggerEnabled()) {
system.GetDebugger().NotifyThreadWatchpoint(thread, *interface->HaltedWatchpoint());
}
thread->RequestSuspend(SuspendType::Debug);
return;
}
// Handle system calls.
if (supervisor_call) {
// Perform call.
Svc::Call(system, interface->GetSvcNumber());
return;
}
// Handle external interrupt sources.
if (interrupt || !m_is_single_core) {
return;
}
}
}
void PhysicalCore::LoadContext(const KThread* thread) {
auto* const process = thread->GetOwnerProcess();
if (!process) {
// Kernel threads do not run on emulated CPU cores.
return;
}
auto* interface = process->GetArmInterface(m_core_index);
if (interface) {
interface->SetContext(thread->GetContext());
interface->SetTpidrroEl0(GetInteger(thread->GetTlsAddress()));
interface->SetWatchpointArray(&process->GetWatchpoints());
}
}
void PhysicalCore::LoadSvcArguments(const KProcess& process, std::span<const uint64_t, 8> args) {
process.GetArmInterface(m_core_index)->SetSvcArguments(args);
}
void PhysicalCore::SaveContext(KThread* thread) const {
auto* const process = thread->GetOwnerProcess();
if (!process) {
// Kernel threads do not run on emulated CPU cores.
return;
}
auto* interface = process->GetArmInterface(m_core_index);
if (interface) {
interface->GetContext(thread->GetContext());
}
}
void PhysicalCore::SaveSvcArguments(KProcess& process, std::span<uint64_t, 8> args) const {
process.GetArmInterface(m_core_index)->GetSvcArguments(args);
}
void PhysicalCore::CloneFpuStatus(KThread* dst) const {
auto* process = dst->GetOwnerProcess();
Svc::ThreadContext ctx{};
process->GetArmInterface(m_core_index)->GetContext(ctx);
dst->GetContext().fpcr = ctx.fpcr;
dst->GetContext().fpsr = ctx.fpsr;
}
void PhysicalCore::LogBacktrace() {
auto* process = GetCurrentProcessPointer(m_kernel);
if (!process) {
return;
}
auto* interface = process->GetArmInterface(m_core_index);
if (interface) {
interface->LogBacktrace(process);
}
}
void PhysicalCore::Idle() {
std::unique_lock lk{m_guard};
m_on_interrupt.wait(lk, [this] { return m_is_interrupted; });
}
bool PhysicalCore::IsInterrupted() const {
return m_is_interrupted;
}
void PhysicalCore::Interrupt() {
// Lock core context.
std::scoped_lock lk{m_guard};
// Load members.
auto* arm_interface = m_arm_interface;
auto* thread = m_current_thread;
// Add interrupt flag.
m_is_interrupted = true;
// Interrupt ourselves.
m_on_interrupt.notify_one();
// If there is no thread running, we are done.
if (arm_interface == nullptr) {
return;
}
// Interrupt the CPU.
arm_interface->SignalInterrupt(thread);
}
void PhysicalCore::ClearInterrupt() {
std::scoped_lock lk{m_guard};
m_is_interrupted = false;
}
} // namespace Kernel