nxemu-cpu: more work for getting NCE closer to working (#265)

This commit is contained in:
NullVector 2026-09-18 22:03:50 +09:30 committed by GitHub
parent 4fc350032d
commit 158d1b4fd9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 190 additions and 258 deletions

2
external/ffmpeg vendored

@ -1 +1 @@
Subproject commit 0c60c9885a66e2ffbeed95986aa434ed920a68ad
Subproject commit 772cea1ac12d5cd11ff17df97dc8a9858d9c1c01

View File

@ -64,7 +64,7 @@ struct SystemModules::Impl :
}
}
bool IsValid() const
bool IsValid() const override
{
return valid;
}

View File

@ -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

View File

@ -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{};
};

View File

@ -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

View File

@ -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.

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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);
}
}

View File

@ -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

View File

@ -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.

View File

@ -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;
}

View File

@ -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);

View File

@ -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.