* Kernel/Process: Notify the CPUs that a new pagetable has been set every time the process they're executing changes. Previously the page table would only be changed when the current CPU's page table was changed, this lead to stale JIT states and the PC going to 0 when context-switching a different core inside the ThreadManager::SwitchContext function because the JIT for a new pagetable is only constructed upon receiving the change notification. * Kernel/Process: Use the relevant CPU's last process to determine when to switch its current process. Previously it was checking the kernel's current_process variable, which gets overwritten every time a CPU runs its slice. The rescheduling happens after all CPUs have run their slice so the code was effectively only checking the last CPU's process.
319 lines
9.0 KiB
C++
319 lines
9.0 KiB
C++
// Copyright 2016 Citra Emulator Project
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <cstring>
|
|
#include <dynarmic/A32/a32.h>
|
|
#include <dynarmic/A32/context.h>
|
|
#include "common/assert.h"
|
|
#include "common/microprofile.h"
|
|
#include "core/arm/dynarmic/arm_dynarmic.h"
|
|
#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
|
|
#include "core/core.h"
|
|
#include "core/core_timing.h"
|
|
#include "core/gdbstub/gdbstub.h"
|
|
#include "core/hle/kernel/svc.h"
|
|
#include "core/memory.h"
|
|
|
|
class DynarmicThreadContext final : public ARM_Interface::ThreadContext {
|
|
public:
|
|
DynarmicThreadContext() {
|
|
Reset();
|
|
}
|
|
~DynarmicThreadContext() override = default;
|
|
|
|
void Reset() override {
|
|
ctx.Regs() = {};
|
|
ctx.SetCpsr(0);
|
|
ctx.ExtRegs() = {};
|
|
ctx.SetFpscr(0);
|
|
fpexc = 0;
|
|
}
|
|
|
|
u32 GetCpuRegister(std::size_t index) const override {
|
|
return ctx.Regs()[index];
|
|
}
|
|
void SetCpuRegister(std::size_t index, u32 value) override {
|
|
ctx.Regs()[index] = value;
|
|
}
|
|
u32 GetCpsr() const override {
|
|
return ctx.Cpsr();
|
|
}
|
|
void SetCpsr(u32 value) override {
|
|
ctx.SetCpsr(value);
|
|
}
|
|
u32 GetFpuRegister(std::size_t index) const override {
|
|
return ctx.ExtRegs()[index];
|
|
}
|
|
void SetFpuRegister(std::size_t index, u32 value) override {
|
|
ctx.ExtRegs()[index] = value;
|
|
}
|
|
u32 GetFpscr() const override {
|
|
return ctx.Fpscr();
|
|
}
|
|
void SetFpscr(u32 value) override {
|
|
ctx.SetFpscr(value);
|
|
}
|
|
u32 GetFpexc() const override {
|
|
return fpexc;
|
|
}
|
|
void SetFpexc(u32 value) override {
|
|
fpexc = value;
|
|
}
|
|
|
|
private:
|
|
friend class ARM_Dynarmic;
|
|
|
|
Dynarmic::A32::Context ctx;
|
|
u32 fpexc;
|
|
};
|
|
|
|
class DynarmicUserCallbacks final : public Dynarmic::A32::UserCallbacks {
|
|
public:
|
|
explicit DynarmicUserCallbacks(ARM_Dynarmic& parent)
|
|
: parent(parent), svc_context(parent.system), memory(parent.memory) {}
|
|
~DynarmicUserCallbacks() = default;
|
|
|
|
std::uint8_t MemoryRead8(VAddr vaddr) override {
|
|
return memory.Read8(vaddr);
|
|
}
|
|
std::uint16_t MemoryRead16(VAddr vaddr) override {
|
|
return memory.Read16(vaddr);
|
|
}
|
|
std::uint32_t MemoryRead32(VAddr vaddr) override {
|
|
return memory.Read32(vaddr);
|
|
}
|
|
std::uint64_t MemoryRead64(VAddr vaddr) override {
|
|
return memory.Read64(vaddr);
|
|
}
|
|
|
|
void MemoryWrite8(VAddr vaddr, std::uint8_t value) override {
|
|
memory.Write8(vaddr, value);
|
|
}
|
|
void MemoryWrite16(VAddr vaddr, std::uint16_t value) override {
|
|
memory.Write16(vaddr, value);
|
|
}
|
|
void MemoryWrite32(VAddr vaddr, std::uint32_t value) override {
|
|
memory.Write32(vaddr, value);
|
|
}
|
|
void MemoryWrite64(VAddr vaddr, std::uint64_t value) override {
|
|
memory.Write64(vaddr, value);
|
|
}
|
|
|
|
void InterpreterFallback(VAddr pc, std::size_t num_instructions) override {
|
|
// Should never happen.
|
|
UNREACHABLE_MSG("InterpeterFallback reached with pc = 0x{:08x}, code = 0x{:08x}, num = {}",
|
|
pc, MemoryReadCode(pc), num_instructions);
|
|
}
|
|
|
|
void CallSVC(std::uint32_t swi) override {
|
|
svc_context.CallSVC(swi);
|
|
}
|
|
|
|
void ExceptionRaised(VAddr pc, Dynarmic::A32::Exception exception) override {
|
|
switch (exception) {
|
|
case Dynarmic::A32::Exception::UndefinedInstruction:
|
|
case Dynarmic::A32::Exception::UnpredictableInstruction:
|
|
break;
|
|
case Dynarmic::A32::Exception::Breakpoint:
|
|
if (GDBStub::IsConnected()) {
|
|
parent.jit->HaltExecution();
|
|
parent.SetPC(pc);
|
|
parent.ServeBreak();
|
|
return;
|
|
}
|
|
break;
|
|
case Dynarmic::A32::Exception::SendEvent:
|
|
case Dynarmic::A32::Exception::SendEventLocal:
|
|
case Dynarmic::A32::Exception::WaitForInterrupt:
|
|
case Dynarmic::A32::Exception::WaitForEvent:
|
|
case Dynarmic::A32::Exception::Yield:
|
|
case Dynarmic::A32::Exception::PreloadData:
|
|
case Dynarmic::A32::Exception::PreloadDataWithIntentToWrite:
|
|
return;
|
|
}
|
|
ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
|
|
static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
|
|
}
|
|
|
|
void AddTicks(std::uint64_t ticks) override {
|
|
parent.GetTimer()->AddTicks(ticks);
|
|
}
|
|
std::uint64_t GetTicksRemaining() override {
|
|
s64 ticks = parent.GetTimer()->GetDowncount();
|
|
return static_cast<u64>(ticks <= 0 ? 0 : ticks);
|
|
}
|
|
|
|
ARM_Dynarmic& parent;
|
|
Kernel::SVCContext svc_context;
|
|
Memory::MemorySystem& memory;
|
|
};
|
|
|
|
ARM_Dynarmic::ARM_Dynarmic(Core::System* system, Memory::MemorySystem& memory, u32 id,
|
|
std::shared_ptr<Core::Timing::Timer> timer)
|
|
: ARM_Interface(id, timer), system(*system), memory(memory),
|
|
cb(std::make_unique<DynarmicUserCallbacks>(*this)) {
|
|
PageTableChanged(memory.GetCurrentPageTable());
|
|
}
|
|
|
|
ARM_Dynarmic::~ARM_Dynarmic() = default;
|
|
|
|
MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64));
|
|
|
|
void ARM_Dynarmic::Run() {
|
|
ASSERT(memory.GetCurrentPageTable() == current_page_table);
|
|
MICROPROFILE_SCOPE(ARM_Jit);
|
|
|
|
jit->Run();
|
|
}
|
|
|
|
void ARM_Dynarmic::Step() {
|
|
jit->Step();
|
|
|
|
if (GDBStub::IsConnected()) {
|
|
ServeBreak();
|
|
}
|
|
}
|
|
|
|
void ARM_Dynarmic::SetPC(u32 pc) {
|
|
jit->Regs()[15] = pc;
|
|
}
|
|
|
|
u32 ARM_Dynarmic::GetPC() const {
|
|
return jit->Regs()[15];
|
|
}
|
|
|
|
u32 ARM_Dynarmic::GetReg(int index) const {
|
|
return jit->Regs()[index];
|
|
}
|
|
|
|
void ARM_Dynarmic::SetReg(int index, u32 value) {
|
|
jit->Regs()[index] = value;
|
|
}
|
|
|
|
u32 ARM_Dynarmic::GetVFPReg(int index) const {
|
|
return jit->ExtRegs()[index];
|
|
}
|
|
|
|
void ARM_Dynarmic::SetVFPReg(int index, u32 value) {
|
|
jit->ExtRegs()[index] = value;
|
|
}
|
|
|
|
u32 ARM_Dynarmic::GetVFPSystemReg(VFPSystemRegister reg) const {
|
|
switch (reg) {
|
|
case VFP_FPSCR:
|
|
return jit->Fpscr();
|
|
case VFP_FPEXC:
|
|
return fpexc;
|
|
}
|
|
UNREACHABLE_MSG("Unknown VFP system register: {}", static_cast<size_t>(reg));
|
|
}
|
|
|
|
void ARM_Dynarmic::SetVFPSystemReg(VFPSystemRegister reg, u32 value) {
|
|
switch (reg) {
|
|
case VFP_FPSCR:
|
|
jit->SetFpscr(value);
|
|
return;
|
|
case VFP_FPEXC:
|
|
fpexc = value;
|
|
return;
|
|
}
|
|
UNREACHABLE_MSG("Unknown VFP system register: {}", static_cast<size_t>(reg));
|
|
}
|
|
|
|
u32 ARM_Dynarmic::GetCPSR() const {
|
|
return jit->Cpsr();
|
|
}
|
|
|
|
void ARM_Dynarmic::SetCPSR(u32 cpsr) {
|
|
jit->SetCpsr(cpsr);
|
|
}
|
|
|
|
u32 ARM_Dynarmic::GetCP15Register(CP15Register reg) {
|
|
switch (reg) {
|
|
case CP15_THREAD_UPRW:
|
|
return cp15_state.cp15_thread_uprw;
|
|
case CP15_THREAD_URO:
|
|
return cp15_state.cp15_thread_uro;
|
|
}
|
|
UNREACHABLE_MSG("Unknown CP15 register: {}", static_cast<size_t>(reg));
|
|
}
|
|
|
|
void ARM_Dynarmic::SetCP15Register(CP15Register reg, u32 value) {
|
|
switch (reg) {
|
|
case CP15_THREAD_UPRW:
|
|
cp15_state.cp15_thread_uprw = value;
|
|
return;
|
|
case CP15_THREAD_URO:
|
|
cp15_state.cp15_thread_uro = value;
|
|
return;
|
|
}
|
|
UNREACHABLE_MSG("Unknown CP15 register: {}", static_cast<size_t>(reg));
|
|
}
|
|
|
|
std::unique_ptr<ARM_Interface::ThreadContext> ARM_Dynarmic::NewContext() const {
|
|
return std::make_unique<DynarmicThreadContext>();
|
|
}
|
|
|
|
void ARM_Dynarmic::SaveContext(const std::unique_ptr<ThreadContext>& arg) {
|
|
DynarmicThreadContext* ctx = dynamic_cast<DynarmicThreadContext*>(arg.get());
|
|
ASSERT(ctx);
|
|
|
|
jit->SaveContext(ctx->ctx);
|
|
ctx->fpexc = fpexc;
|
|
}
|
|
|
|
void ARM_Dynarmic::LoadContext(const std::unique_ptr<ThreadContext>& arg) {
|
|
const DynarmicThreadContext* ctx = dynamic_cast<DynarmicThreadContext*>(arg.get());
|
|
ASSERT(ctx);
|
|
|
|
jit->LoadContext(ctx->ctx);
|
|
fpexc = ctx->fpexc;
|
|
}
|
|
|
|
void ARM_Dynarmic::PrepareReschedule() {
|
|
if (jit->IsExecuting()) {
|
|
jit->HaltExecution();
|
|
}
|
|
}
|
|
|
|
void ARM_Dynarmic::ClearInstructionCache() {
|
|
for (const auto& j : jits) {
|
|
j.second->ClearCache();
|
|
}
|
|
}
|
|
|
|
void ARM_Dynarmic::InvalidateCacheRange(u32 start_address, std::size_t length) {
|
|
jit->InvalidateCacheRange(start_address, length);
|
|
}
|
|
|
|
void ARM_Dynarmic::PageTableChanged(Memory::PageTable* new_page_table) {
|
|
current_page_table = new_page_table;
|
|
|
|
auto iter = jits.find(current_page_table);
|
|
if (iter != jits.end()) {
|
|
jit = iter->second.get();
|
|
return;
|
|
}
|
|
|
|
auto new_jit = MakeJit();
|
|
jit = new_jit.get();
|
|
jits.emplace(current_page_table, std::move(new_jit));
|
|
}
|
|
|
|
void ARM_Dynarmic::ServeBreak() {
|
|
Kernel::Thread* thread = system.Kernel().GetCurrentThreadManager().GetCurrentThread();
|
|
SaveContext(thread->context);
|
|
GDBStub::Break();
|
|
GDBStub::SendTrap(thread, 5);
|
|
}
|
|
|
|
std::unique_ptr<Dynarmic::A32::Jit> ARM_Dynarmic::MakeJit() {
|
|
Dynarmic::A32::UserConfig config;
|
|
config.callbacks = cb.get();
|
|
config.page_table = ¤t_page_table->pointers;
|
|
config.coprocessors[15] = std::make_shared<DynarmicCP15>(cp15_state);
|
|
config.define_unpredictable_behaviour = true;
|
|
return std::make_unique<Dynarmic::A32::Jit>(config);
|
|
}
|