nxemu-cpu: more work for getting NCE closer to working (#265)
This commit is contained in:
parent
4fc350032d
commit
158d1b4fd9
|
|
@ -1 +1 @@
|
|||
Subproject commit 0c60c9885a66e2ffbeed95986aa434ed920a68ad
|
||||
Subproject commit 772cea1ac12d5cd11ff17df97dc8a9858d9c1c01
|
||||
|
|
@ -64,7 +64,7 @@ struct SystemModules::Impl :
|
|||
}
|
||||
}
|
||||
|
||||
bool IsValid() const
|
||||
bool IsValid() const override
|
||||
{
|
||||
return valid;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cinttypes>
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
|
||||
#include "common/signal_chain.h"
|
||||
#include "core/arm/nce/arm_nce.h"
|
||||
#include "core/arm/nce/interpreter_visitor.h"
|
||||
#include "core/arm/nce/patcher.h"
|
||||
#include "core/core.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "arm_dynarmic.h"
|
||||
#include "nce/arm_nce.h"
|
||||
#include "nce/interpreter_visitor.h"
|
||||
#include "nce/patcher.h"
|
||||
#include "yuzu_common/literals.h"
|
||||
#include "yuzu_common/signal_chain.h"
|
||||
#include "yuzu_common/yuzu_assert.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <sys/syscall.h>
|
||||
|
|
@ -25,7 +24,6 @@ struct sigaction g_orig_bus_action;
|
|||
struct sigaction g_orig_segv_action;
|
||||
|
||||
// Verify assembly offsets.
|
||||
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
|
||||
static_assert(offsetof(NativeExecutionParameters, native_context) == TpidrEl0NativeContext);
|
||||
static_assert(offsetof(NativeExecutionParameters, lock) == TpidrEl0Lock);
|
||||
static_assert(offsetof(NativeExecutionParameters, magic) == TpidrEl0TlsMagic);
|
||||
|
|
@ -40,30 +38,24 @@ fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) {
|
|||
|
||||
using namespace Common::Literals;
|
||||
constexpr u32 StackSize = 128_KiB;
|
||||
constexpr u64 NcePageSize = 0x1000;
|
||||
constexpr u64 NcePageMask = NcePageSize - 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
void* ArmNce::RestoreGuestContext(void* raw_context) {
|
||||
// Retrieve the host context.
|
||||
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
|
||||
|
||||
// Thread-local parameters will be located in x9.
|
||||
auto* tpidr = reinterpret_cast<NativeExecutionParameters*>(host_ctx.regs[9]);
|
||||
auto* guest_ctx = static_cast<GuestContext*>(tpidr->native_context);
|
||||
|
||||
// Retrieve the host floating point state.
|
||||
auto* fpctx = GetFloatingPointState(host_ctx);
|
||||
|
||||
// Save host callee-saved registers.
|
||||
std::memcpy(guest_ctx->host_ctx.host_saved_vregs.data(), &fpctx->vregs[8],
|
||||
sizeof(guest_ctx->host_ctx.host_saved_vregs));
|
||||
std::memcpy(guest_ctx->host_ctx.host_saved_regs.data(), &host_ctx.regs[19],
|
||||
sizeof(guest_ctx->host_ctx.host_saved_regs));
|
||||
std::memcpy(guest_ctx->host_ctx.host_saved_vregs.data(), &fpctx->vregs[8], sizeof(guest_ctx->host_ctx.host_saved_vregs));
|
||||
std::memcpy(guest_ctx->host_ctx.host_saved_regs.data(), &host_ctx.regs[19], sizeof(guest_ctx->host_ctx.host_saved_regs));
|
||||
|
||||
// Save stack pointer.
|
||||
guest_ctx->host_ctx.host_sp = host_ctx.sp;
|
||||
|
||||
// Restore all guest state except tpidr_el0.
|
||||
host_ctx.sp = guest_ctx->sp;
|
||||
host_ctx.pc = guest_ctx->pc;
|
||||
host_ctx.pstate = guest_ctx->pstate;
|
||||
|
|
@ -71,19 +63,13 @@ void* ArmNce::RestoreGuestContext(void* raw_context) {
|
|||
fpctx->fpsr = guest_ctx->fpsr;
|
||||
std::memcpy(host_ctx.regs, guest_ctx->cpu_registers.data(), sizeof(host_ctx.regs));
|
||||
std::memcpy(fpctx->vregs, guest_ctx->vector_registers.data(), sizeof(fpctx->vregs));
|
||||
|
||||
// Return the new thread-local storage pointer.
|
||||
return tpidr;
|
||||
}
|
||||
|
||||
void ArmNce::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) {
|
||||
// Retrieve the host context.
|
||||
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
|
||||
|
||||
// Retrieve the host floating point state.
|
||||
auto* fpctx = GetFloatingPointState(host_ctx);
|
||||
|
||||
// Save all guest registers except tpidr_el0.
|
||||
std::memcpy(guest_ctx->cpu_registers.data(), host_ctx.regs, sizeof(host_ctx.regs));
|
||||
std::memcpy(guest_ctx->vector_registers.data(), fpctx->vregs, sizeof(fpctx->vregs));
|
||||
guest_ctx->fpsr = fpctx->fpsr;
|
||||
|
|
@ -92,19 +78,14 @@ void ArmNce::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) {
|
|||
guest_ctx->pc = host_ctx.pc;
|
||||
guest_ctx->sp = host_ctx.sp;
|
||||
|
||||
// Restore stack pointer.
|
||||
host_ctx.sp = guest_ctx->host_ctx.host_sp;
|
||||
|
||||
// Restore host callee-saved registers.
|
||||
std::memcpy(&host_ctx.regs[19], guest_ctx->host_ctx.host_saved_regs.data(),
|
||||
sizeof(guest_ctx->host_ctx.host_saved_regs));
|
||||
std::memcpy(&fpctx->vregs[8], guest_ctx->host_ctx.host_saved_vregs.data(),
|
||||
sizeof(guest_ctx->host_ctx.host_saved_vregs));
|
||||
|
||||
// Return from the call on exit by setting pc to x30.
|
||||
host_ctx.pc = guest_ctx->host_ctx.host_saved_regs[11];
|
||||
|
||||
// Clear esr_el1 and return it.
|
||||
host_ctx.regs[0] = guest_ctx->esr_el1.exchange(0);
|
||||
}
|
||||
|
||||
|
|
@ -112,62 +93,31 @@ bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, voi
|
|||
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
|
||||
auto* info = static_cast<siginfo_t*>(raw_info);
|
||||
|
||||
// We can't handle the access, so determine why we crashed.
|
||||
const bool is_prefetch_abort = host_ctx.pc == reinterpret_cast<u64>(info->si_addr);
|
||||
|
||||
// For data aborts, skip the instruction and return to guest code.
|
||||
// This will allow games to continue in many scenarios where they would otherwise crash.
|
||||
if (!is_prefetch_abort) {
|
||||
host_ctx.pc += 4;
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is a prefetch abort.
|
||||
guest_ctx->esr_el1.fetch_or(static_cast<u64>(HaltReason::PrefetchAbort));
|
||||
guest_ctx->esr_el1.fetch_or((uint64_t)TranslateDynarmicHaltReason(CpuHaltReason::PrefetchAbort));
|
||||
|
||||
// Forcibly mark the context as locked. We are still running.
|
||||
// We may race with SignalInterrupt here:
|
||||
// - If we lose the race, then SignalInterrupt will send us a signal we are masking,
|
||||
// and it will do nothing when it is unmasked, as we have already left guest code.
|
||||
// - If we win the race, then SignalInterrupt will wait for us to unlock first.
|
||||
auto& thread_params = guest_ctx->parent->m_running_thread->GetNativeExecutionParameters();
|
||||
thread_params.lock.store(SpinLockLocked);
|
||||
thread_params.lock = SpinLockLocked;
|
||||
|
||||
// Return to host.
|
||||
SaveGuestContext(guest_ctx, raw_context);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
|
||||
auto* fpctx = GetFloatingPointState(host_ctx);
|
||||
auto& memory = guest_ctx->system->ApplicationMemory();
|
||||
|
||||
// Match and execute an instruction.
|
||||
auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx);
|
||||
if (next_pc) {
|
||||
host_ctx.pc = *next_pc;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We couldn't handle the access.
|
||||
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
|
||||
bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info, void* raw_context)
|
||||
{
|
||||
UNIMPLEMENTED();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||
auto* info = static_cast<siginfo_t*>(raw_info);
|
||||
|
||||
// Try to handle an invalid access.
|
||||
// TODO: handle accesses which split a page?
|
||||
const Common::ProcessAddress addr =
|
||||
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
|
||||
if (guest_ctx->system->ApplicationMemory().InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
|
||||
// We handled the access successfully and are returning to guest code.
|
||||
return true;
|
||||
}
|
||||
|
||||
// We couldn't handle the access.
|
||||
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
|
||||
UNIMPLEMENTED();
|
||||
return false;
|
||||
}
|
||||
|
||||
void ArmNce::HandleHostAlignmentFault(int sig, void* raw_info, void* raw_context) {
|
||||
|
|
@ -178,79 +128,47 @@ void ArmNce::HandleHostAccessFault(int sig, void* raw_info, void* raw_context) {
|
|||
return g_orig_segv_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
|
||||
}
|
||||
|
||||
void ArmNce::LockThread(Kernel::KThread* thread) {
|
||||
void ArmNce::LockThread(IKernelThread* thread) {
|
||||
auto* thread_params = &thread->GetNativeExecutionParameters();
|
||||
LockThreadParameters(thread_params);
|
||||
}
|
||||
|
||||
void ArmNce::UnlockThread(Kernel::KThread* thread) {
|
||||
void ArmNce::UnlockThread(IKernelThread* thread) {
|
||||
auto* thread_params = &thread->GetNativeExecutionParameters();
|
||||
UnlockThreadParameters(thread_params);
|
||||
}
|
||||
|
||||
HaltReason ArmNce::RunThread(Kernel::KThread* thread) {
|
||||
// Check if we're already interrupted.
|
||||
// If we are, we can just return immediately.
|
||||
HaltReason hr = static_cast<HaltReason>(m_guest_ctx.esr_el1.exchange(0));
|
||||
if (True(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Get the thread context.
|
||||
auto* thread_params = &thread->GetNativeExecutionParameters();
|
||||
auto* process = thread->GetOwnerProcess();
|
||||
|
||||
// Assign current members.
|
||||
m_running_thread = thread;
|
||||
m_guest_ctx.parent = this;
|
||||
thread_params->native_context = &m_guest_ctx;
|
||||
thread_params->tpidr_el0 = m_guest_ctx.tpidr_el0;
|
||||
thread_params->tpidrro_el0 = m_guest_ctx.tpidrro_el0;
|
||||
thread_params->is_running = true;
|
||||
|
||||
// TODO: finding and creating the post handler needs to be locked
|
||||
// to deal with dynamic loading of NROs.
|
||||
const auto& post_handlers = process->GetPostHandlers();
|
||||
if (auto it = post_handlers.find(m_guest_ctx.pc); it != post_handlers.end()) {
|
||||
hr = ReturnToRunCodeByTrampoline(thread_params, &m_guest_ctx, it->second);
|
||||
} else {
|
||||
hr = ReturnToRunCodeByExceptionLevelChange(m_thread_id, thread_params);
|
||||
}
|
||||
|
||||
// Unload members.
|
||||
// The thread does not change, so we can persist the old reference.
|
||||
m_running_thread = nullptr;
|
||||
m_guest_ctx.tpidr_el0 = thread_params->tpidr_el0;
|
||||
thread_params->native_context = nullptr;
|
||||
thread_params->is_running = false;
|
||||
|
||||
// Return the halt reason.
|
||||
return hr;
|
||||
CpuHaltReason ArmNce::RunThread(IKernelThread* thread) {
|
||||
UNIMPLEMENTED();
|
||||
return CpuHaltReason::StepThread;
|
||||
}
|
||||
|
||||
HaltReason ArmNce::StepThread(Kernel::KThread* thread) {
|
||||
return HaltReason::StepThread;
|
||||
CpuHaltReason ArmNce::StepThread(IKernelThread* /*thread*/) {
|
||||
return CpuHaltReason::StepThread;
|
||||
}
|
||||
|
||||
u32 ArmNce::GetSvcNumber() const {
|
||||
uint32_t ArmNce::GetSvcNumber() const {
|
||||
return m_guest_ctx.svc;
|
||||
}
|
||||
|
||||
void ArmNce::GetSvcArguments(std::span<uint64_t, 8> args) const {
|
||||
void ArmNce::GetSvcArguments(uint64_t (&args)[8]) const {
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
args[i] = m_guest_ctx.cpu_registers[i];
|
||||
}
|
||||
}
|
||||
|
||||
void ArmNce::SetSvcArguments(std::span<const uint64_t, 8> args) {
|
||||
void ArmNce::SetSvcArguments(const uint64_t (&args)[8]) {
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
m_guest_ctx.cpu_registers[i] = args[i];
|
||||
}
|
||||
}
|
||||
|
||||
ArmNce::ArmNce(System& system, bool uses_wall_clock, std::size_t core_index)
|
||||
: ArmInterface{uses_wall_clock}, m_system{system}, m_core_index{core_index} {
|
||||
m_guest_ctx.system = &m_system;
|
||||
ArmNce::ArmNce(ICoreSystem& system, bool /*uses_wall_clock*/, IKernelProcess& process, std::size_t core_index) :
|
||||
m_system(system),
|
||||
m_core_index(core_index)
|
||||
{
|
||||
m_guest_ctx.memory = &process.GetMemory();
|
||||
m_guest_ctx.parent = this;
|
||||
}
|
||||
|
||||
ArmNce::~ArmNce() = default;
|
||||
|
|
@ -313,12 +231,15 @@ void ArmNce::Initialize() {
|
|||
});
|
||||
}
|
||||
|
||||
void ArmNce::SetTpidrroEl0(u64 value) {
|
||||
void ArmNce::SetTpidrroEl0(u64 value)
|
||||
{
|
||||
m_guest_ctx.tpidrro_el0 = value;
|
||||
}
|
||||
|
||||
void ArmNce::GetContext(Kernel::Svc::ThreadContext& ctx) const {
|
||||
for (size_t i = 0; i < 29; i++) {
|
||||
void ArmNce::GetContext(CpuThreadContext& ctx) const
|
||||
{
|
||||
for (size_t i = 0; i < 29; i++)
|
||||
{
|
||||
ctx.r[i] = m_guest_ctx.cpu_registers[i];
|
||||
}
|
||||
ctx.fp = m_guest_ctx.cpu_registers[29];
|
||||
|
|
@ -326,14 +247,16 @@ void ArmNce::GetContext(Kernel::Svc::ThreadContext& ctx) const {
|
|||
ctx.sp = m_guest_ctx.sp;
|
||||
ctx.pc = m_guest_ctx.pc;
|
||||
ctx.pstate = m_guest_ctx.pstate;
|
||||
ctx.v = m_guest_ctx.vector_registers;
|
||||
std::memcpy(ctx.v, m_guest_ctx.vector_registers.data(), sizeof(ctx.v));
|
||||
ctx.fpcr = m_guest_ctx.fpcr;
|
||||
ctx.fpsr = m_guest_ctx.fpsr;
|
||||
ctx.tpidr = m_guest_ctx.tpidr_el0;
|
||||
}
|
||||
|
||||
void ArmNce::SetContext(const Kernel::Svc::ThreadContext& ctx) {
|
||||
for (size_t i = 0; i < 29; i++) {
|
||||
void ArmNce::SetContext(const CpuThreadContext& ctx)
|
||||
{
|
||||
for (size_t i = 0; i < 29; i++)
|
||||
{
|
||||
m_guest_ctx.cpu_registers[i] = ctx.r[i];
|
||||
}
|
||||
m_guest_ctx.cpu_registers[29] = ctx.fp;
|
||||
|
|
@ -341,31 +264,31 @@ void ArmNce::SetContext(const Kernel::Svc::ThreadContext& ctx) {
|
|||
m_guest_ctx.sp = ctx.sp;
|
||||
m_guest_ctx.pc = ctx.pc;
|
||||
m_guest_ctx.pstate = ctx.pstate;
|
||||
m_guest_ctx.vector_registers = ctx.v;
|
||||
std::memcpy(m_guest_ctx.vector_registers.data(), ctx.v, sizeof(ctx.v));
|
||||
m_guest_ctx.fpcr = ctx.fpcr;
|
||||
m_guest_ctx.fpsr = ctx.fpsr;
|
||||
m_guest_ctx.tpidr_el0 = ctx.tpidr;
|
||||
}
|
||||
|
||||
void ArmNce::SignalInterrupt(Kernel::KThread* thread) {
|
||||
// Add break loop condition.
|
||||
m_guest_ctx.esr_el1.fetch_or(static_cast<u64>(HaltReason::BreakLoop));
|
||||
void ArmNce::SignalInterrupt(IKernelThread* thread)
|
||||
{
|
||||
m_guest_ctx.esr_el1.fetch_or((uint64_t)TranslateDynarmicHaltReason(CpuHaltReason::BreakLoop));
|
||||
|
||||
// Lock the thread context.
|
||||
auto* params = &thread->GetNativeExecutionParameters();
|
||||
LockThreadParameters(params);
|
||||
|
||||
if (params->is_running) {
|
||||
// We should signal to the running thread.
|
||||
// The running thread will unlock the thread context.
|
||||
if (params->is_running)
|
||||
{
|
||||
syscall(SYS_tkill, m_thread_id, BreakFromRunCodeSignal);
|
||||
} else {
|
||||
// If the thread is no longer running, we have nothing to do.
|
||||
}
|
||||
else
|
||||
{
|
||||
UnlockThreadParameters(params);
|
||||
}
|
||||
}
|
||||
|
||||
void ArmNce::ClearInstructionCache() {
|
||||
void ArmNce::ClearInstructionCache()
|
||||
{
|
||||
// TODO: This is not possible to implement correctly on Linux because
|
||||
// we do not have any access to ic iallu.
|
||||
|
||||
|
|
@ -373,8 +296,14 @@ void ArmNce::ClearInstructionCache() {
|
|||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
}
|
||||
|
||||
void ArmNce::InvalidateCacheRange(u64 addr, std::size_t size) {
|
||||
void ArmNce::InvalidateCacheRange(uint64_t /*addr*/, uint64_t /*size*/)
|
||||
{
|
||||
this->ClearInstructionCache();
|
||||
}
|
||||
|
||||
void ArmNce::Release()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
|
|
|||
|
|
@ -5,90 +5,87 @@
|
|||
|
||||
#include <mutex>
|
||||
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/arm/nce/guest_context.h"
|
||||
|
||||
namespace Core::Memory {
|
||||
class Memory;
|
||||
}
|
||||
#include "dynarmic/interface/halt_reason.h"
|
||||
#include "nce/guest_context.h"
|
||||
#include "yuzu_common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
class System;
|
||||
|
||||
class ArmNce final : public ArmInterface {
|
||||
class ArmNce final : public ICpuCore {
|
||||
public:
|
||||
ArmNce(System& system, bool uses_wall_clock, std::size_t core_index);
|
||||
~ArmNce() override;
|
||||
ArmNce(ICoreSystem & system, bool uses_wall_clock, IKernelProcess & process, std::size_t core_index);
|
||||
~ArmNce();
|
||||
|
||||
void Initialize() override;
|
||||
|
||||
Architecture GetArchitecture() const override {
|
||||
return Architecture::AArch64;
|
||||
ProcessorArchitecture GetArchitecture() const override
|
||||
{
|
||||
return ProcessorArchitecture::AArch64;
|
||||
}
|
||||
|
||||
HaltReason RunThread(Kernel::KThread* thread) override;
|
||||
HaltReason StepThread(Kernel::KThread* thread) override;
|
||||
CpuHaltReason RunThread(IKernelThread * thread) override;
|
||||
CpuHaltReason StepThread(IKernelThread * thread) override;
|
||||
|
||||
void GetContext(Kernel::Svc::ThreadContext& ctx) const override;
|
||||
void SetContext(const Kernel::Svc::ThreadContext& ctx) override;
|
||||
void SetTpidrroEl0(u64 value) override;
|
||||
void GetContext(CpuThreadContext & ctx) const override;
|
||||
void SetContext(const CpuThreadContext & ctx) override;
|
||||
void SetTpidrroEl0(uint64_t value) override;
|
||||
|
||||
void GetSvcArguments(std::span<uint64_t, 8> args) const override;
|
||||
void SetSvcArguments(std::span<const uint64_t, 8> args) override;
|
||||
u32 GetSvcNumber() const override;
|
||||
void GetSvcArguments(uint64_t (&args)[8]) const override;
|
||||
void SetSvcArguments(const uint64_t (&args)[8]) override;
|
||||
uint32_t GetSvcNumber() const override;
|
||||
|
||||
void SignalInterrupt(Kernel::KThread* thread) override;
|
||||
void SignalInterrupt(IKernelThread * thread) override;
|
||||
void ClearInstructionCache() override;
|
||||
void InvalidateCacheRange(u64 addr, std::size_t size) override;
|
||||
void InvalidateCacheRange(uint64_t addr, uint64_t size) override;
|
||||
|
||||
void LockThread(Kernel::KThread* thread) override;
|
||||
void UnlockThread(Kernel::KThread* thread) override;
|
||||
void LockThread(IKernelThread * thread) override;
|
||||
void UnlockThread(IKernelThread * thread) override;
|
||||
|
||||
protected:
|
||||
const Kernel::DebugWatchpoint* HaltedWatchpoint() const override {
|
||||
const CpuDebugWatchpoint * HaltedWatchpoint() const override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void RewindBreakpointInstruction() override {}
|
||||
void RewindBreakpointInstruction() override
|
||||
{
|
||||
}
|
||||
void SetWatchpointArray(const CpuDebugWatchpoint * /*watchpoints*/, uint32_t /*count*/) override
|
||||
{
|
||||
}
|
||||
|
||||
void Release() override;
|
||||
|
||||
private:
|
||||
// Assembly definitions.
|
||||
static HaltReason ReturnToRunCodeByTrampoline(void* tpidr, GuestContext* ctx,
|
||||
u64 trampoline_addr);
|
||||
static HaltReason ReturnToRunCodeByExceptionLevelChange(int tid, void* tpidr);
|
||||
static Dynarmic::HaltReason ReturnToRunCodeByTrampoline(void * tpidr, GuestContext * ctx, uint64_t trampoline_addr);
|
||||
static Dynarmic::HaltReason ReturnToRunCodeByExceptionLevelChange(int tid, void * tpidr);
|
||||
|
||||
static void ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void* info,
|
||||
void* raw_context);
|
||||
static void BreakFromRunCodeSignalHandler(int sig, void* info, void* raw_context);
|
||||
static void GuestAlignmentFaultSignalHandler(int sig, void* info, void* raw_context);
|
||||
static void GuestAccessFaultSignalHandler(int sig, void* info, void* raw_context);
|
||||
static void ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void * info, void * raw_context);
|
||||
static void BreakFromRunCodeSignalHandler(int sig, void * info, void * raw_context);
|
||||
static void GuestAlignmentFaultSignalHandler(int sig, void * info, void * raw_context);
|
||||
static void GuestAccessFaultSignalHandler(int sig, void * info, void * raw_context);
|
||||
|
||||
static void LockThreadParameters(void* tpidr);
|
||||
static void UnlockThreadParameters(void* tpidr);
|
||||
static void LockThreadParameters(void * tpidr);
|
||||
static void UnlockThreadParameters(void * tpidr);
|
||||
|
||||
private:
|
||||
// C++ implementation functions for assembly definitions.
|
||||
static void* RestoreGuestContext(void* raw_context);
|
||||
static void SaveGuestContext(GuestContext* ctx, void* raw_context);
|
||||
static bool HandleFailedGuestFault(GuestContext* ctx, void* info, void* raw_context);
|
||||
static bool HandleGuestAlignmentFault(GuestContext* ctx, void* info, void* raw_context);
|
||||
static bool HandleGuestAccessFault(GuestContext* ctx, void* info, void* raw_context);
|
||||
static void HandleHostAlignmentFault(int sig, void* info, void* raw_context);
|
||||
static void HandleHostAccessFault(int sig, void* info, void* raw_context);
|
||||
static void * RestoreGuestContext(void * raw_context);
|
||||
static void SaveGuestContext(GuestContext * ctx, void * raw_context);
|
||||
static bool HandleFailedGuestFault(GuestContext * ctx, void * info, void * raw_context);
|
||||
static bool HandleGuestAlignmentFault(GuestContext * ctx, void * info, void * raw_context);
|
||||
static bool HandleGuestAccessFault(GuestContext * ctx, void * info, void * raw_context);
|
||||
static void HandleHostAlignmentFault(int sig, void * info, void * raw_context);
|
||||
static void HandleHostAccessFault(int sig, void * info, void * raw_context);
|
||||
|
||||
public:
|
||||
Core::System& m_system;
|
||||
ICoreSystem & m_system;
|
||||
|
||||
// Members set on initialization.
|
||||
std::size_t m_core_index{};
|
||||
pid_t m_thread_id{-1};
|
||||
|
||||
// Core context.
|
||||
GuestContext m_guest_ctx{};
|
||||
Kernel::KThread* m_running_thread{};
|
||||
IKernelThread * m_running_thread{};
|
||||
|
||||
// Stack for signal processing.
|
||||
std::unique_ptr<u8[]> m_stack{};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
/* SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project */
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "core/arm/nce/arm_nce_asm_definitions.h"
|
||||
#include "nce/arm_nce_asm_definitions.h"
|
||||
|
||||
#define LOAD_IMMEDIATE_32(reg, val) \
|
||||
mov reg, #(((val) >> 0x00) & 0xFFFF); \
|
||||
movk reg, #(((val) >> 0x10) & 0xFFFF), lsl #16
|
||||
|
||||
|
||||
/* static HaltReason Core::ArmNce::ReturnToRunCodeByTrampoline(void* tpidr, Core::GuestContext* ctx, u64 trampoline_addr) */
|
||||
/* static Dynarmic::HaltReason Core::ArmNce::ReturnToRunCodeByTrampoline(void* tpidr, Core::GuestContext* ctx, u64 trampoline_addr) */
|
||||
.section .text._ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm, "ax", %progbits
|
||||
.global _ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm
|
||||
.type _ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm, %function
|
||||
|
|
@ -49,7 +49,7 @@ _ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm:
|
|||
br x2
|
||||
|
||||
|
||||
/* static HaltReason Core::ArmNce::ReturnToRunCodeByExceptionLevelChange(int tid, void* tpidr) */
|
||||
/* static Dynarmic::HaltReason Core::ArmNce::ReturnToRunCodeByExceptionLevelChange(int tid, void* tpidr) */
|
||||
.section .text._ZN4Core6ArmNce37ReturnToRunCodeByExceptionLevelChangeEiPv, "ax", %progbits
|
||||
.global _ZN4Core6ArmNce37ReturnToRunCodeByExceptionLevelChangeEiPv
|
||||
.type _ZN4Core6ArmNce37ReturnToRunCodeByExceptionLevelChangeEiPv, %function
|
||||
|
|
|
|||
|
|
@ -3,17 +3,17 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/arm/nce/arm_nce_asm_definitions.h"
|
||||
#include "nce/arm_nce_asm_definitions.h"
|
||||
#include "yuzu_common/common_types.h"
|
||||
#include <nxemu-module-spec/cpu.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ArmNce;
|
||||
class System;
|
||||
|
||||
struct HostContext {
|
||||
alignas(16) std::array<u64, 12> host_saved_regs{};
|
||||
|
|
@ -36,8 +36,8 @@ struct GuestContext {
|
|||
std::atomic<u64> esr_el1{};
|
||||
u32 nzcv{};
|
||||
u32 svc{};
|
||||
System* system{};
|
||||
ArmNce* parent{};
|
||||
IMemory * memory{};
|
||||
ArmNce * parent{};
|
||||
};
|
||||
|
||||
// Verify assembly offsets.
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 merryhime <https://mary.rs>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "yuzu_common/bit_cast.h"
|
||||
#include "interpreter_visitor.h"
|
||||
#include "yuzu_common/bit_cast.h"
|
||||
#include "yuzu_common/logging/log.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
|
|
@ -793,7 +794,7 @@ bool InterpreterVisitor::LDR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3
|
|||
return this->SIMDOffset(scale, shift, opc_0, Rm, option, Rn, Vt);
|
||||
}
|
||||
|
||||
std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context,
|
||||
std::optional<u64> MatchAndExecuteOneInstruction(IMemory& memory, mcontext_t* context,
|
||||
fpsimd_context* fpsimd_context) {
|
||||
// Construct the interpreter.
|
||||
std::span<u64, 31> regs(reinterpret_cast<u64*>(context->regs), 31);
|
||||
|
|
|
|||
|
|
@ -4,20 +4,20 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <signal.h>
|
||||
#include <span>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "core/arm/nce/visitor_base.h"
|
||||
#include "nce/visitor_base.h"
|
||||
#include "yuzu_common/common_types.h"
|
||||
#include <nxemu-module-spec/cpu.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
namespace Memory {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
class InterpreterVisitor final : public VisitorBase {
|
||||
public:
|
||||
explicit InterpreterVisitor(Core::Memory::Memory& memory, std::span<u64, 31> regs,
|
||||
explicit InterpreterVisitor(IMemory& memory, std::span<u64, 31> regs,
|
||||
std::span<u128, 32> fpsimd_regs, u64& sp, const u64& pc)
|
||||
: m_memory(memory), m_regs(regs), m_fpsimd_regs(fpsimd_regs), m_sp(sp), m_pc(pc) {}
|
||||
~InterpreterVisitor() override = default;
|
||||
|
|
@ -90,14 +90,14 @@ public:
|
|||
Vec Vt) override;
|
||||
|
||||
private:
|
||||
Core::Memory::Memory& m_memory;
|
||||
IMemory& m_memory;
|
||||
std::span<u64, 31> m_regs;
|
||||
std::span<u128, 32> m_fpsimd_regs;
|
||||
u64& m_sp;
|
||||
const u64& m_pc;
|
||||
};
|
||||
|
||||
std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context,
|
||||
std::optional<u64> MatchAndExecuteOneInstruction(IMemory& memory, mcontext_t* context,
|
||||
fpsimd_context* fpsimd_context);
|
||||
|
||||
} // namespace Core
|
||||
|
|
|
|||
|
|
@ -1,24 +1,21 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/arm/nce/arm_nce.h"
|
||||
#include "core/arm/nce/guest_context.h"
|
||||
#include "core/arm/nce/instructions.h"
|
||||
#include "core/arm/nce/patcher.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "arm_dynarmic.h"
|
||||
#include "nce/guest_context.h"
|
||||
#include "nce/instructions.h"
|
||||
#include "nce/patcher.h"
|
||||
#include "yuzu_common/alignment.h"
|
||||
#include "yuzu_common/arm64/native_clock.h"
|
||||
#include "yuzu_common/bit_cast.h"
|
||||
#include "yuzu_common/literals.h"
|
||||
#include "yuzu_common/yuzu_assert.h"
|
||||
|
||||
namespace Core::NCE {
|
||||
|
||||
using namespace Common::Literals;
|
||||
using namespace oaknut::util;
|
||||
|
||||
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
|
||||
|
||||
constexpr size_t MaxRelativeBranch = 128_MiB;
|
||||
constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32);
|
||||
|
||||
|
|
@ -38,10 +35,9 @@ Patcher::Patcher() : c(m_patch_instructions) {
|
|||
|
||||
Patcher::~Patcher() = default;
|
||||
|
||||
bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
|
||||
const Kernel::CodeSet::Segment& code) {
|
||||
bool Patcher::PatchText(const uint8_t * program_image, uint64_t image_size, uint64_t code_offset,
|
||||
uint64_t code_size) {
|
||||
// If we have patched modules but cannot reach the new module, then it needs its own patcher.
|
||||
const size_t image_size = program_image.size();
|
||||
if (total_program_size + image_size > MaxRelativeBranch && total_program_size > 0) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -55,7 +51,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
|
|||
curr_patch->m_branch_to_module_relocations.push_back({0, 0});
|
||||
|
||||
// Retrieve text segment data.
|
||||
const auto text = std::span{program_image}.subspan(code.offset, code.size);
|
||||
const auto text = std::span{program_image + code_offset, code_size};
|
||||
const auto text_words =
|
||||
std::span<const u32>{reinterpret_cast<const u32*>(text.data()), text.size() / sizeof(u32)};
|
||||
|
||||
|
|
@ -119,15 +115,14 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
|
||||
const Kernel::CodeSet::Segment& code,
|
||||
Kernel::PhysicalMemory& program_image,
|
||||
EntryTrampolines* out_trampolines) {
|
||||
bool Patcher::RelocateAndCopy(uint64_t load_base, uint64_t code_offset, uint64_t code_size,
|
||||
uint8_t * program_image, uint64_t * image_size,
|
||||
EntryTrampolines * out_trampolines) {
|
||||
const size_t patch_size = GetSectionSize();
|
||||
const size_t image_size = program_image.size();
|
||||
const size_t current_image_size = *image_size;
|
||||
|
||||
// Retrieve text segment data.
|
||||
const auto text = std::span{program_image}.subspan(code.offset, code.size);
|
||||
const auto text = std::span{program_image + code_offset, code_size};
|
||||
const auto text_words =
|
||||
std::span<u32>{reinterpret_cast<u32*>(text.data()), text.size() / sizeof(u32)};
|
||||
|
||||
|
|
@ -151,17 +146,17 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
|
|||
|
||||
const auto RebasePatch = [&](ptrdiff_t patch_offset) {
|
||||
if (mode == PatchMode::PreText) {
|
||||
return GetInteger(load_base) + patch_offset;
|
||||
return load_base + patch_offset;
|
||||
} else {
|
||||
return GetInteger(load_base) + total_program_size + patch_offset;
|
||||
return load_base + total_program_size + patch_offset;
|
||||
}
|
||||
};
|
||||
|
||||
const auto RebasePc = [&](uintptr_t module_offset) {
|
||||
if (mode == PatchMode::PreText) {
|
||||
return GetInteger(load_base) + patch_size + module_offset;
|
||||
return load_base + patch_size + module_offset;
|
||||
} else {
|
||||
return GetInteger(load_base) + module_offset;
|
||||
return load_base + module_offset;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -193,18 +188,18 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
|
|||
|
||||
// Remove the patched module size from the total. This is done so total_program_size
|
||||
// always represents the distance from the currently patched module to the patch section.
|
||||
total_program_size -= image_size;
|
||||
total_program_size -= current_image_size;
|
||||
|
||||
// Only copy to the program image of the last module
|
||||
if (m_relocate_module_index == modules.size()) {
|
||||
if (this->mode == PatchMode::PreText) {
|
||||
ASSERT(image_size == total_program_size);
|
||||
std::memcpy(program_image.data(), m_patch_instructions.data(),
|
||||
ASSERT(current_image_size == total_program_size);
|
||||
std::memcpy(program_image, m_patch_instructions.data(),
|
||||
m_patch_instructions.size() * sizeof(u32));
|
||||
} else {
|
||||
program_image.resize(image_size + patch_size);
|
||||
std::memcpy(program_image.data() + image_size, m_patch_instructions.data(),
|
||||
std::memcpy(program_image + current_image_size, m_patch_instructions.data(),
|
||||
m_patch_instructions.size() * sizeof(u32));
|
||||
*image_size = current_image_size + patch_size;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -213,7 +208,8 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
|
|||
}
|
||||
|
||||
size_t Patcher::GetSectionSize() const noexcept {
|
||||
return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), Core::Memory::YUZU_PAGESIZE);
|
||||
constexpr u64 NcePageSize = 0x1000;
|
||||
return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), NcePageSize);
|
||||
}
|
||||
|
||||
void Patcher::WriteLoadContext() {
|
||||
|
|
@ -310,7 +306,7 @@ void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) {
|
|||
c.STR(W2, X1, offsetof(GuestContext, svc));
|
||||
|
||||
// We are calling a SVC. Clear esr_el1 and return it.
|
||||
static_assert(std::is_same_v<std::underlying_type_t<HaltReason>, u64>);
|
||||
static_assert(sizeof(std::underlying_type_t<Dynarmic::HaltReason>) <= sizeof(u64));
|
||||
oaknut::Label retry;
|
||||
c.ADD(X2, X1, offsetof(GuestContext, esr_el1));
|
||||
c.l(retry);
|
||||
|
|
@ -319,7 +315,7 @@ void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) {
|
|||
c.CBNZ(W3, retry);
|
||||
|
||||
// Add "calling SVC" flag. Since this is X0, this is now our return value.
|
||||
c.ORR(X0, X0, static_cast<u64>(HaltReason::SupervisorCall));
|
||||
c.ORR(X0, X0, static_cast<u64>(TranslateDynarmicHaltReason(CpuHaltReason::SupervisorCall)));
|
||||
|
||||
// Offset the GuestContext pointer to the HostContext member.
|
||||
// STP has limited range of [-512, 504] which we can't reach otherwise
|
||||
|
|
|
|||
|
|
@ -66,9 +66,9 @@ void PatchCollection::Relocate(int32_t patch_index, uint64_t load_base, uint8_t
|
|||
|
||||
if (IKernelProcess * process = m_modules.OperatingSystem().CurrentProcess())
|
||||
{
|
||||
for (const auto & [module_text, patch_text] : trampolines)
|
||||
for (Core::NCE::EntryTrampolines::const_iterator it = trampolines.begin(); it != trampolines.end(); ++it)
|
||||
{
|
||||
process->RegisterPostHandler(module_text, patch_text);
|
||||
process->RegisterPostHandler(it->first, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ nxinterface IMemory
|
|||
virtual uint32_t Read32(uint64_t addr) = 0;
|
||||
virtual uint64_t Read64(uint64_t addr) = 0;
|
||||
virtual bool ReadBlock(uint64_t src_addr, void * dest_buffer, uint64_t size) = 0;
|
||||
virtual bool WriteBlock(uint64_t dest_addr, const void * src_buffer, uint64_t size) = 0;
|
||||
|
||||
virtual void Write8(uint64_t addr, uint8_t value) = 0;
|
||||
virtual void Write16(uint64_t addr, uint16_t value) = 0;
|
||||
|
|
@ -131,9 +132,20 @@ nxinterface IKernelProcess
|
|||
virtual void LogBacktrace(ICpuCore & cpuCore) = 0;
|
||||
};
|
||||
|
||||
struct NativeExecutionParameters
|
||||
{
|
||||
uint64_t tpidr_el0{};
|
||||
uint64_t tpidrro_el0{};
|
||||
void * native_context{};
|
||||
uint32_t lock{1};
|
||||
uint32_t is_running{};
|
||||
uint32_t magic{0x4D45584E}; // 'NXEM'
|
||||
};
|
||||
|
||||
nxinterface IKernelThread
|
||||
{
|
||||
virtual IKernelProcess * GetOwnerProcess() const = 0;
|
||||
virtual NativeExecutionParameters & GetNativeExecutionParameters() = 0;
|
||||
};
|
||||
|
||||
struct CpuThreadContext
|
||||
|
|
|
|||
|
|
@ -103,6 +103,11 @@ KThread::KThread(KernelCore& kernel)
|
|||
: KAutoObjectWithSlabHeapAndContainer{kernel}, m_activity_pause_lock{kernel} {}
|
||||
KThread::~KThread() = default;
|
||||
|
||||
IKernelProcess * KThread::GetOwnerProcess() const
|
||||
{
|
||||
return (IKernelProcess *)m_parent;
|
||||
}
|
||||
|
||||
Result KThread::Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress user_stack_top,
|
||||
s32 prio, s32 virt_core, KProcess* owner, ThreadType type) {
|
||||
// Assert parameters are valid.
|
||||
|
|
|
|||
|
|
@ -365,10 +365,7 @@ public:
|
|||
return m_parent;
|
||||
}
|
||||
|
||||
IKernelProcess * GetOwnerProcess() const override
|
||||
{
|
||||
return (IKernelProcess *)m_parent;
|
||||
}
|
||||
IKernelProcess * GetOwnerProcess() const override;
|
||||
|
||||
bool IsUserThread() const
|
||||
{
|
||||
|
|
@ -758,18 +755,7 @@ public:
|
|||
}
|
||||
|
||||
public:
|
||||
// TODO: This shouldn't be defined in kernel namespace
|
||||
struct NativeExecutionParameters
|
||||
{
|
||||
u64 tpidr_el0{};
|
||||
u64 tpidrro_el0{};
|
||||
void * native_context{};
|
||||
std::atomic<u32> lock{1};
|
||||
bool is_running{};
|
||||
u32 magic{Common::MakeMagic('Y', 'U', 'Z', 'U')};
|
||||
};
|
||||
|
||||
NativeExecutionParameters & GetNativeExecutionParameters()
|
||||
NativeExecutionParameters & GetNativeExecutionParameters() override
|
||||
{
|
||||
return m_native_execution_parameters;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1112,6 +1112,11 @@ bool Memory::WriteBlock(const Common::ProcessAddress dest_addr, const void* src_
|
|||
return impl->WriteBlock(dest_addr, src_buffer, size);
|
||||
}
|
||||
|
||||
bool Memory::WriteBlock(uint64_t dest_addr, const void * src_buffer, uint64_t size)
|
||||
{
|
||||
return WriteBlock(Common::ProcessAddress{dest_addr}, src_buffer, (std::size_t)size);
|
||||
}
|
||||
|
||||
bool Memory::WriteBlockUnsafe(const Common::ProcessAddress dest_addr, const void* src_buffer, const std::size_t size)
|
||||
{
|
||||
return impl->WriteBlockUnsafe(dest_addr, src_buffer, size);
|
||||
|
|
|
|||
|
|
@ -349,6 +349,7 @@ public:
|
|||
* current process' address space.
|
||||
*/
|
||||
bool ReadBlock(uint64_t src_addr, void * dest_buffer, uint64_t size) override;
|
||||
bool WriteBlock(uint64_t dest_addr, const void * src_buffer, uint64_t size) override;
|
||||
|
||||
/**
|
||||
* Reads a contiguous block of bytes from the current process' address space.
|
||||
|
|
|
|||
Loading…
Reference in New Issue