726 lines
No EOL
26 KiB
C++
726 lines
No EOL
26 KiB
C++
/* (c) 2025 Wrapper Inc. - The New Emulation Library */
|
|
|
|
#include <fstream>
|
|
#include "fmt/format.h"
|
|
|
|
#include "shared/os_wrapper.hpp"
|
|
#include "dynemu/sys_emulator.hpp"
|
|
|
|
#include "dynarmic/interface/A32/a32.h"
|
|
#include "dynarmic/interface/A32/config.h"
|
|
#include "dynarmic/interface/A32/coprocessor.h"
|
|
#include "dynarmic/interface/exclusive_monitor.h"
|
|
#include "dynarmic/interface/optimization_flags.h"
|
|
#include "dynarmic/frontend/A32/a32_ir_emitter.h"
|
|
|
|
#include "mcl/assert.hpp"
|
|
|
|
#define DEBUG 0
|
|
#define BLOCK_ALLIGN 0x10000
|
|
#define ALIGN_BLOCK(x) ((x + (BLOCK_ALLIGN - 1)) & ~(BLOCK_ALLIGN - 1))
|
|
#define EMU_MIN(a,b) ((a) < (b) ? (a) : (b))
|
|
#define EMU_MAX(a,b) ((a) > (b) ? (a) : (b))
|
|
|
|
namespace Dynemu {
|
|
struct FlashPartition {
|
|
u32 start_vaddr;
|
|
u32 size;
|
|
std::unique_ptr<u8[]> data;
|
|
};
|
|
|
|
struct FlashLoader {
|
|
u16 magic;
|
|
u32 count;
|
|
std::unique_ptr<FlashPartition[]> data;
|
|
};
|
|
|
|
/* 01 - Private */
|
|
MemMap * MyEmulator::find_memory_map_by_vaddr(u32 vaddr) {
|
|
for (int i = 0; i < mem_map_index; i++) {
|
|
if (vaddr >= mem_map[i].start && vaddr < (mem_map[i].start + mem_map[i].size))
|
|
return &mem_map[i];
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
MMIO * MyEmulator::find_mmio_by_vaddr(u32 vaddr) {
|
|
for (int i = 0; i < mmio_index; i++) {
|
|
if (vaddr >= mmio_map[i].start && vaddr < (mmio_map[i].start + mmio_map[i].size))
|
|
return &mmio_map[i];
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::optional<u32> MyEmulator::handle_mmio_read(u32 vaddr, u8 access) {
|
|
if (auto mmio = find_mmio_by_vaddr(vaddr); mmio != nullptr) {
|
|
#if DEBUG
|
|
printf("Handling MMIO READ for 0x%08x\n", vaddr);
|
|
#endif
|
|
return mmio->read(vaddr - mmio->start, access);
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
bool MyEmulator::handle_mmio_write(u32 vaddr, u32 value, u8 access) {
|
|
if (auto mmio = find_mmio_by_vaddr(vaddr); mmio != nullptr) {
|
|
#if DEBUG
|
|
printf("Handling MMIO WRITE for 0x%08x\n", vaddr);
|
|
#endif
|
|
mmio->write(vaddr - mmio->start, value, access);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool MyEmulator::check_memory_access(u32 vaddr, u32 size) {
|
|
if (auto mmio = find_mmio_by_vaddr(vaddr); mmio != nullptr) {
|
|
return ((mmio->start + mmio->size) - vaddr) >= size;
|
|
}
|
|
|
|
if (auto mmap = find_memory_map_by_vaddr(vaddr); mmap != nullptr) {
|
|
return ((mmap->start + mmap->size) - vaddr) >= size;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* 02 - Public */
|
|
void MyEmulator::CreateMemoryMap(u32 vaddr, u32 size) {
|
|
ASSERT_MSG(find_mmio_by_vaddr(vaddr) == nullptr, "Cannot create memory map on MMIO-mapped addresses");
|
|
if (find_memory_map_by_vaddr(vaddr) == nullptr)
|
|
mem_map[mem_map_index++] = {vaddr, size, std::vector<u8>(size)};
|
|
}
|
|
|
|
size_t MyEmulator::UploadToMemory(const std::string &filename, std::int64_t dest, u32 size) {
|
|
std::ifstream file(filename, std::ios::binary);
|
|
u8 buf[2048];
|
|
u32 offset = 0;
|
|
u32 read_n;
|
|
|
|
if (!file.is_open()) {
|
|
std::cerr << fmt::format("dynemu: unable to open code file {}: {}", filename, std::strerror(errno)) << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
if (dest != -1) {
|
|
file.seekg(0, file.end);
|
|
size_t fileSize = file.tellg();
|
|
file.seekg(0, file.beg);
|
|
|
|
auto mmap = find_memory_map_by_vaddr((u32)dest);
|
|
bool fitsOnMemoryMap = mmap == nullptr ? (size >= fileSize) : (((mmap->start + mmap->size) - dest) >= fileSize);
|
|
|
|
if (!fitsOnMemoryMap) {
|
|
std::cerr << "dynemu: code file too large to fit into memory" << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
CreateMemoryMap((u32)dest, ALIGN_BLOCK(size));
|
|
|
|
while ((size_t)file.tellg() < fileSize) {
|
|
read_n = EMU_MIN(fileSize - file.tellg(), sizeof(buf));
|
|
file.read((char *)buf, read_n);
|
|
MemoryWriteBytes((u32)(dest + offset), buf, read_n);
|
|
offset += read_n;
|
|
}
|
|
|
|
return fileSize;
|
|
} else {
|
|
FlashLoader fl{};
|
|
u32 codeSize = 0;
|
|
|
|
file.read((char *)&fl.magic, sizeof(fl.magic));
|
|
file.read((char *)&fl.count, sizeof(fl.count));
|
|
|
|
if (fl.magic != 0x4c46) {
|
|
std::cerr << "dynemu: invalid FL magic check" << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
fl.data = std::make_unique<FlashPartition[]>(fl.count);
|
|
|
|
for (u32 i = 0; i < fl.count; i++) {
|
|
file.read((char *)&fl.data.get()[i].start_vaddr, sizeof(fl.data.get()[i].start_vaddr));
|
|
file.read((char *)&fl.data.get()[i].size, sizeof(fl.data.get()[i].size));
|
|
fl.data.get()[i].data = std::make_unique<u8[]>(fl.data.get()[i].size);
|
|
file.read((char *)fl.data.get()[i].data.get(), fl.data.get()[i].size);
|
|
|
|
codeSize += fl.data.get()[i].size;
|
|
|
|
CreateMemoryMap(fl.data.get()[i].start_vaddr, ALIGN_BLOCK(fl.data.get()[i].size));
|
|
MemoryWriteBytes(fl.data.get()[i].start_vaddr, fl.data.get()[i].data.get(), fl.data.get()[i].size);
|
|
}
|
|
|
|
return codeSize;
|
|
}
|
|
}
|
|
|
|
bool MyEmulator::UploadFLSFromBytes(const u8 *fls) {
|
|
FlashLoader fl{};
|
|
|
|
memcpy(&fl.magic, fls, sizeof(fl.magic));
|
|
memcpy(&fl.count, fls + sizeof(fl.magic), sizeof(fl.count));
|
|
|
|
if (fl.magic != 0x4c46) {
|
|
std::cerr << "dynemu: invalid FL magic check" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
fl.data = std::make_unique<FlashPartition[]>(fl.count);
|
|
|
|
auto offset = sizeof(fl.magic) + sizeof(fl.count);
|
|
|
|
for (u32 i = 0; i < fl.count; i++) {
|
|
memcpy(&fl.data.get()[i].start_vaddr, fls + offset, sizeof(fl.data.get()[i].start_vaddr));
|
|
memcpy(&fl.data.get()[i].size, fls + offset + sizeof(fl.data.get()[i].start_vaddr), sizeof(fl.data.get()[i].size));
|
|
fl.data.get()[i].data = std::make_unique<u8[]>(fl.data.get()[i].size);
|
|
memcpy((char *)fl.data.get()[i].data.get(), fls + offset + sizeof(fl.data.get()[i].start_vaddr) + sizeof(fl.data.get()[i].size), fl.data.get()[i].size);
|
|
|
|
offset += sizeof(fl.data.get()[i].start_vaddr) + sizeof(fl.data.get()[i].size) + fl.data.get()[i].size;
|
|
|
|
CreateMemoryMap(fl.data.get()[i].start_vaddr, ALIGN_BLOCK(fl.data.get()[i].size));
|
|
MemoryWriteBytes(fl.data.get()[i].start_vaddr, fl.data.get()[i].data.get(), fl.data.get()[i].size);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void MyEmulator::UploadOSStub(u32 dest, u32 mem_start, u32 mem_size) {
|
|
if (big_endian) {
|
|
MemoryWriteBytes(dest, OS_LoopLoader_BE.data(), (u32)ALIGN_BLOCK(OS_LoopLoader_BE.size()));
|
|
} else {
|
|
MemoryWriteBytes(dest, OS_LoopLoader.data(), (u32)ALIGN_BLOCK(OS_LoopLoader.size()));
|
|
}
|
|
|
|
MemoryWrite32(dest + MEM_INIT_REG_OFFSET, mem_start);
|
|
MemoryWrite32(dest + MEM_INIT_REG_SIZE, mem_size);
|
|
}
|
|
|
|
void MyEmulator::CreateMMIO(u32 addr_start, u32 addr_size, const MMIO_Read &read, const MMIO_Write &write) {
|
|
ASSERT_MSG(find_memory_map_by_vaddr(addr_start) == nullptr, "Cannot create MMIO map on memory-mapped address");
|
|
if (find_mmio_by_vaddr(addr_start) == nullptr)
|
|
mmio_map[mmio_index++] = {addr_start, addr_size, read, write};
|
|
}
|
|
|
|
bool MyEmulator::PreCodeReadHook(bool is_thumb, u32 pc, Dynarmic::A32::IREmitter& ir) {
|
|
#if DEBUG
|
|
std::cout << fmt::format("executing at {:#010x}", pc) << std::endl;
|
|
std::cout << fmt::format("REG P0 {:#010x}", cpu->Regs()[0]) << std::endl;
|
|
std::cout << fmt::format("REG P1 {:#010x}", cpu->Regs()[1]) << std::endl;
|
|
std::cout << fmt::format("REG P2 {:#010x}", cpu->Regs()[2]) << std::endl;
|
|
std::cout << fmt::format("REG P12 {:#010x}", cpu->Regs()[12]) << std::endl;
|
|
std::cout << fmt::format("REG P15 {:#010x}", cpu->Regs()[15]) << std::endl;
|
|
#endif
|
|
|
|
thumb_mode = is_thumb;
|
|
|
|
if (return_mode && (pc >= 0xfffffffc || pc <= 0xffffffff)) {
|
|
cpu->HaltExecution(Dynarmic::HaltReason::Step);
|
|
ir.SetTerm(Dynarmic::IR::Term::CheckHalt{Dynarmic::IR::Term::ReturnToDispatch{}});
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::optional<u32> MyEmulator::MemoryReadCode(u32 vaddr, bool is_thumb) {
|
|
if (!check_memory_access(vaddr, 4)) {
|
|
std::cerr << fmt::format("dynemu: Invalid memory fetch at {:#010x}", vaddr) << std::endl;
|
|
return std::nullopt;
|
|
}
|
|
|
|
u32 temp;
|
|
|
|
if (auto ret = handle_mmio_read(vaddr, 4); ret.has_value()) {
|
|
temp = ret.value();
|
|
} else {
|
|
temp = MemoryRead32(vaddr);
|
|
}
|
|
|
|
if (big_endian && is_thumb) {
|
|
u32 temp_t_be;
|
|
|
|
temp_t_be = (temp >> 16) & 0xff;
|
|
temp_t_be |= ((temp >> 24) & 0xff) << 8;
|
|
temp_t_be |= (temp & 0xff) << 16;
|
|
temp_t_be |= ((temp >> 8) & 0xff) << 24;
|
|
|
|
return temp_t_be;
|
|
}
|
|
|
|
return temp;
|
|
}
|
|
|
|
u8 MyEmulator::MemoryRead8(u32 vaddr) {
|
|
if (!check_memory_access(vaddr, 1)) {
|
|
std::cerr << fmt::format("dynemu: Invalid memory read at {:#010x}", vaddr) << std::endl;
|
|
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
|
|
return -1; // Undefined behavior can occur
|
|
}
|
|
|
|
#if DEBUG
|
|
if (cpu->IsExecuting()) printf("Reading at 0x%08x\n", vaddr);
|
|
#endif
|
|
|
|
if (auto ret = handle_mmio_read(vaddr, 1); ret.has_value())
|
|
return ret.value();
|
|
|
|
auto mmap = find_memory_map_by_vaddr(vaddr);
|
|
return mmap->data[vaddr - mmap->start];
|
|
}
|
|
|
|
u16 MyEmulator::MemoryRead16(u32 vaddr) {
|
|
if (!check_memory_access(vaddr, 2)) {
|
|
std::cerr << fmt::format("dynemu: Invalid memory read at {:#010x}", vaddr) << std::endl;
|
|
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
|
|
return -1; // Undefined behavior can occur
|
|
}
|
|
|
|
if (auto ret = handle_mmio_read(vaddr, 2); ret.has_value())
|
|
return ret.value();
|
|
|
|
u16 temp;
|
|
|
|
if (big_endian) {
|
|
temp = MemoryRead8(vaddr + 1);
|
|
temp |= MemoryRead8(vaddr) << 8;
|
|
} else {
|
|
temp = MemoryRead8(vaddr);
|
|
temp |= MemoryRead8(vaddr + 1) << 8;
|
|
}
|
|
|
|
return temp;
|
|
}
|
|
|
|
u32 MyEmulator::MemoryRead32(u32 vaddr) {
|
|
if (!check_memory_access(vaddr, 4)) {
|
|
std::cerr << fmt::format("dynemu: Invalid memory read at {:#010x}", vaddr) << std::endl;
|
|
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
|
|
return -1; // Undefined behavior can occur
|
|
}
|
|
|
|
if (auto ret = handle_mmio_read(vaddr, 4); ret.has_value())
|
|
return ret.value();
|
|
|
|
u32 temp;
|
|
|
|
if (big_endian) {
|
|
temp = MemoryRead8(vaddr + 3);
|
|
temp |= MemoryRead8(vaddr + 2) << 8;
|
|
temp |= MemoryRead8(vaddr + 1) << 16;
|
|
temp |= MemoryRead8(vaddr) << 24;
|
|
} else {
|
|
temp = MemoryRead8(vaddr);
|
|
temp |= MemoryRead8(vaddr + 1) << 8;
|
|
temp |= MemoryRead8(vaddr + 2) << 16;
|
|
temp |= MemoryRead8(vaddr + 3) << 24;
|
|
}
|
|
|
|
return temp;
|
|
}
|
|
|
|
u64 MyEmulator::MemoryRead64(u32 vaddr) {
|
|
ASSERT_FALSE("target is not a 64-bit device");
|
|
}
|
|
|
|
void MyEmulator::MemoryWrite8(u32 vaddr, u8 value) {
|
|
if (!check_memory_access(vaddr, 1)) {
|
|
std::cerr << fmt::format("dynemu: Invalid memory write at {:#010x}", vaddr) << std::endl;
|
|
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
|
|
return; // Undefined behavior can occur
|
|
}
|
|
|
|
#if DEBUG
|
|
if (cpu->IsExecuting()) printf("Writing at 0x%08x 0x%02x\n", vaddr, value);
|
|
#endif
|
|
|
|
if (handle_mmio_write(vaddr, value, 1))
|
|
return;
|
|
|
|
auto mmap = find_memory_map_by_vaddr(vaddr);
|
|
mmap->data[vaddr - mmap->start] = value;
|
|
}
|
|
|
|
void MyEmulator::MemoryWrite16(u32 vaddr, u16 value) {
|
|
if (!check_memory_access(vaddr, 2)) {
|
|
std::cerr << fmt::format("dynemu: Invalid memory write at {:#010x}", vaddr) << std::endl;
|
|
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
|
|
return; // Undefined behavior can occur
|
|
}
|
|
|
|
if (handle_mmio_write(vaddr, value, 2))
|
|
return;
|
|
|
|
if (big_endian) {
|
|
MemoryWrite8(vaddr, value >> 8);
|
|
MemoryWrite8(vaddr + 1, value);
|
|
} else {
|
|
MemoryWrite8(vaddr, value);
|
|
MemoryWrite8(vaddr + 1, value >> 8);
|
|
}
|
|
}
|
|
|
|
void MyEmulator::MemoryWrite32(u32 vaddr, u32 value) {
|
|
if (!check_memory_access(vaddr, 4)) {
|
|
std::cerr << fmt::format("dynemu: Invalid memory write at {:#010x}", vaddr) << std::endl;
|
|
cpu->HaltExecution(Dynarmic::HaltReason::MemoryAbort);
|
|
return; // Undefined behavior can occur
|
|
}
|
|
|
|
if (handle_mmio_write(vaddr, value, 4))
|
|
return;
|
|
|
|
if (big_endian) {
|
|
MemoryWrite8(vaddr, value >> 24);
|
|
MemoryWrite8(vaddr + 1, value >> 16);
|
|
MemoryWrite8(vaddr + 2, value >> 8);
|
|
MemoryWrite8(vaddr + 3, value);
|
|
} else {
|
|
MemoryWrite8(vaddr, value);
|
|
MemoryWrite8(vaddr + 1, value >> 8);
|
|
MemoryWrite8(vaddr + 2, value >> 16);
|
|
MemoryWrite8(vaddr + 3, value >> 24);
|
|
}
|
|
}
|
|
|
|
void MyEmulator::MemoryWrite64(u32 vaddr, u64 value) {
|
|
ASSERT_FALSE("target is not a 64-bit device");
|
|
}
|
|
|
|
void MyEmulator::MemoryReadBytes(u32 vaddr, u8 *output, u32 size) {
|
|
ASSERT_MSG(find_mmio_by_vaddr(vaddr) == nullptr, "You're trying to read a MMIO mapped address. Use coresponding MMIO routines instead.");
|
|
ASSERT_MSG(check_memory_access(vaddr, size), "Invalid memory read");
|
|
|
|
auto mmap = find_memory_map_by_vaddr(vaddr);
|
|
memcpy(output, &mmap->data[vaddr - mmap->start], size);
|
|
}
|
|
|
|
|
|
void MyEmulator::MemoryWriteBytes(u32 vaddr, const u8 *input, u32 size) {
|
|
ASSERT_MSG(find_mmio_by_vaddr(vaddr) == nullptr, "You're trying to write a MMIO mapped address. Use coresponding MMIO routines instead.");
|
|
ASSERT_MSG(check_memory_access(vaddr, size), "Invalid memory write");
|
|
|
|
auto mmap = find_memory_map_by_vaddr(vaddr);
|
|
memcpy(&mmap->data[vaddr - mmap->start], input, size);
|
|
}
|
|
|
|
u32 MyEmulator::Execute(u32 pc, bool return_mode) {
|
|
if (return_mode)
|
|
cpu->Regs()[14] = 0xfffffffc;
|
|
|
|
this->return_mode = return_mode;
|
|
cpu->Regs()[15] = pc & ~1;
|
|
cpu->SetCpsr((pc & 1) ? 0x30 : 0x1d0);
|
|
|
|
Dynarmic::HaltReason returnCode = cpu->Run();
|
|
cpu->ClearCache();
|
|
|
|
if (returnCode == Dynarmic::HaltReason::MemoryAbort)
|
|
throw std::runtime_error("attempted to access outside memory bounds");
|
|
|
|
return cpu->Regs()[0];
|
|
}
|
|
|
|
std::future<u32> MyEmulator::Spawn(u32 pc, bool return_mode) {
|
|
return std::async(std::launch::async, [this, pc, return_mode] {
|
|
return Execute(pc, return_mode);
|
|
});
|
|
}
|
|
|
|
std::optional<Callback> CP14::CompileInternalOperation(bool two, unsigned opc1, CoprocReg CRd, CoprocReg CRn, CoprocReg CRm, unsigned opc2) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
// as MCR (Write Coprocessor), when output is u32 pointer, write to data
|
|
CallbackOrAccessOneWord CP14::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, unsigned opc2) {
|
|
// const std::lock_guard<std::mutex> lock(dcc_lock);
|
|
|
|
switch (state.dcc_type) {
|
|
case DCCType::ARM7_ARM9:
|
|
if (!two && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 0) {
|
|
switch (CRn) {
|
|
case CoprocReg::C0:
|
|
return &state.dcc_status;
|
|
|
|
case CoprocReg::C1:
|
|
return Callback{
|
|
[](void* cp14_state, std::uint32_t write_value, std::uint32_t word2_unused) -> std::uint64_t {
|
|
CP14State& state = *reinterpret_cast<CP14State*>(cp14_state);
|
|
|
|
#if DEBUG
|
|
printf("DEBUG WRITE: 0x%08x\n", write_value);
|
|
#endif
|
|
|
|
state.dcc_write_data = write_value;
|
|
state.dcc_status |= 0b10;
|
|
return 0;
|
|
},
|
|
reinterpret_cast<void*>(&state),
|
|
};
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DCCType::ARM11_CORTEX:
|
|
if (!two && opc1 == 0 && CRn == CoprocReg::C0 && opc2 == 2) {
|
|
switch (CRm) {
|
|
case CoprocReg::C0:
|
|
return &dummy_value;
|
|
|
|
case CoprocReg::C1:
|
|
return &dummy_value;
|
|
|
|
case CoprocReg::C2:
|
|
return &state.dcc_status;
|
|
|
|
case CoprocReg::C3:
|
|
return Callback{
|
|
[](void* cp14_state, std::uint32_t write_value, std::uint32_t word2_unused) -> std::uint64_t {
|
|
CP14State& state = *reinterpret_cast<CP14State*>(cp14_state);
|
|
|
|
#if DEBUG
|
|
printf("DEBUG WRITE: 0x%08x\n", write_value);
|
|
#endif
|
|
|
|
state.dcc_write_data = write_value;
|
|
state.dcc_status |= (0b01 << 29);
|
|
return 0;
|
|
},
|
|
reinterpret_cast<void*>(&state),
|
|
};
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
std::cerr << "dynemu: something is wrong with CP14 state" << std::endl;
|
|
return CallbackOrAccessOneWord{};
|
|
}
|
|
|
|
CallbackOrAccessTwoWords CP14::CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) {
|
|
return CallbackOrAccessTwoWords{};
|
|
}
|
|
|
|
// as MRC (Read Coprocessor), when output is u32 pointer, read from data
|
|
CallbackOrAccessOneWord CP14::CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, unsigned opc2) {
|
|
// const std::lock_guard<std::mutex> lock(dcc_lock);
|
|
|
|
switch (state.dcc_type) {
|
|
case DCCType::ARM7_ARM9:
|
|
if (!two && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 0) {
|
|
switch (CRn) {
|
|
case CoprocReg::C0:
|
|
return &state.dcc_status;
|
|
|
|
case CoprocReg::C1:
|
|
return Callback{
|
|
[](void* cp14_state, std::uint32_t word1_unused, std::uint32_t word2_unused) -> std::uint64_t {
|
|
CP14State& state = *reinterpret_cast<CP14State*>(cp14_state);
|
|
|
|
#if DEBUG
|
|
printf("DEBUG READ: 0x%08x\n", state.dcc_read_data);
|
|
#endif
|
|
|
|
u32 temp = state.dcc_read_data;
|
|
state.dcc_status &= ~0b01;
|
|
return temp;
|
|
},
|
|
reinterpret_cast<void*>(&state),
|
|
};
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DCCType::ARM11_CORTEX:
|
|
if (!two && opc1 == 0 && CRn == CoprocReg::C0 && opc2 == 2) {
|
|
switch (CRm) {
|
|
case CoprocReg::C0:
|
|
return Callback{
|
|
[](void* cp14_state, std::uint32_t word1_unused, std::uint32_t word2_unused) -> std::uint64_t {
|
|
CP14State& state = *reinterpret_cast<CP14State*>(cp14_state);
|
|
|
|
#if DEBUG
|
|
printf("DEBUG READ: 0x%08x\n", state.dcc_read_data);
|
|
#endif
|
|
|
|
u32 temp = state.dcc_read_data;
|
|
state.dcc_status &= ~(0b10 << 29);
|
|
return temp;
|
|
},
|
|
reinterpret_cast<void*>(&state),
|
|
};
|
|
|
|
case CoprocReg::C1:
|
|
return &dummy_value;
|
|
|
|
case CoprocReg::C2:
|
|
return &state.dcc_status;
|
|
|
|
case CoprocReg::C3:
|
|
return &dummy_value;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
std::cerr << "dynemu: something is wrong with CP14 state" << std::endl;
|
|
return CallbackOrAccessOneWord{};
|
|
}
|
|
|
|
CallbackOrAccessTwoWords CP14::CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) {
|
|
return CallbackOrAccessTwoWords{};
|
|
}
|
|
|
|
std::optional<Callback> CP14::CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd, std::optional<std::uint8_t> option) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<Callback> CP14::CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd, std::optional<std::uint8_t> option) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
CP14::CP14(DCCType dcc_type, CP14State &state) : state(state) {
|
|
switch (dcc_type) {
|
|
case DCCType::ARM7_ARM9:
|
|
state.dcc_status = 0b01;
|
|
break;
|
|
|
|
case DCCType::ARM11_CORTEX:
|
|
state.dcc_status = 0b10 << 29;
|
|
break;
|
|
}
|
|
state.dcc_type = dcc_type;
|
|
state.dcc_write_data = 0;
|
|
state.dcc_read_data = 0;
|
|
}
|
|
|
|
void CP14::Send(u32 value) {
|
|
const std::lock_guard<std::mutex> lock(dcc_lock);
|
|
|
|
switch (state.dcc_type) {
|
|
case DCCType::ARM7_ARM9:
|
|
while (state.dcc_status & 0b01);
|
|
state.dcc_read_data = value;
|
|
state.dcc_status |= 0b01;
|
|
break;
|
|
|
|
case DCCType::ARM11_CORTEX:
|
|
while (state.dcc_status & (0b10 << 29));
|
|
state.dcc_read_data = value;
|
|
state.dcc_status |= (0b10 << 29);
|
|
break;
|
|
}
|
|
}
|
|
|
|
u32 CP14::Get() {
|
|
const std::lock_guard<std::mutex> lock(dcc_lock);
|
|
u32 temp;
|
|
|
|
switch (state.dcc_type) {
|
|
case DCCType::ARM7_ARM9:
|
|
while (!(state.dcc_status & 0b10));
|
|
temp = state.dcc_write_data;
|
|
state.dcc_status &= ~0b10;
|
|
break;
|
|
|
|
case DCCType::ARM11_CORTEX:
|
|
while (!(state.dcc_status & (0b01 << 29)));
|
|
temp = state.dcc_write_data;
|
|
state.dcc_status &= ~(0b01 << 29);
|
|
break;
|
|
}
|
|
|
|
return temp;
|
|
}
|
|
|
|
class CP15 final : public Dynarmic::A32::Coprocessor {
|
|
using Callback = Dynarmic::A32::Coprocessor::Callback;
|
|
using CoprocReg = Dynarmic::A32::CoprocReg;
|
|
using CallbackOrAccessOneWord = Dynarmic::A32::Coprocessor::CallbackOrAccessOneWord;
|
|
using CallbackOrAccessTwoWords = Dynarmic::A32::Coprocessor::CallbackOrAccessTwoWords;
|
|
|
|
u32 threadStoragePointer; // Pointer to thread-local storage
|
|
|
|
std::optional<Callback> CompileInternalOperation(bool two, unsigned opc1, CoprocReg CRd,
|
|
CoprocReg CRn, CoprocReg CRm,
|
|
unsigned opc2) override {
|
|
return std::nullopt;
|
|
}
|
|
|
|
CallbackOrAccessOneWord CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn,
|
|
CoprocReg CRm, unsigned opc2) override {
|
|
return CallbackOrAccessOneWord{};
|
|
}
|
|
|
|
CallbackOrAccessTwoWords CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) override {
|
|
return CallbackOrAccessTwoWords{};
|
|
}
|
|
|
|
CallbackOrAccessOneWord CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn,
|
|
CoprocReg CRm, unsigned opc2) override {
|
|
// Stores a pointer to thread-local storage, accessed via mrc p15, 0, rd, c13, c0, 3
|
|
if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 3) {
|
|
return &threadStoragePointer;
|
|
}
|
|
|
|
return CallbackOrAccessOneWord{};
|
|
}
|
|
|
|
CallbackOrAccessTwoWords CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) override {
|
|
return CallbackOrAccessTwoWords{};
|
|
}
|
|
|
|
std::optional<Callback> CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd,
|
|
std::optional<std::uint8_t> option) override {
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<Callback> CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd,
|
|
std::optional<std::uint8_t> option) override {
|
|
return std::nullopt;
|
|
}
|
|
|
|
public:
|
|
void setTLSBase(u32 value) {
|
|
threadStoragePointer = value;
|
|
}
|
|
};
|
|
|
|
Emulator::Emulator(DCCType dcc_type, bool no_opts, bool big_endian) : env(big_endian) {
|
|
Dynarmic::A32::UserConfig user_config;
|
|
|
|
cp14 = std::make_shared<CP14>(dcc_type, cp14_state);
|
|
|
|
user_config.enable_cycle_counting = false;
|
|
if (no_opts)
|
|
user_config.optimizations = Dynarmic::no_optimizations;
|
|
|
|
user_config.callbacks = &env;
|
|
user_config.check_halt_on_memory_access = true;
|
|
user_config.coprocessors[14] = cp14;
|
|
user_config.coprocessors[15] = std::make_shared<CP15>();
|
|
user_config.arch_version = Dynarmic::A32::ArchVersion::v7;
|
|
|
|
mon = std::make_unique<Dynarmic::ExclusiveMonitor>(1);
|
|
user_config.global_monitor = mon.get();
|
|
|
|
cpu = std::make_unique<Dynarmic::A32::Jit>(user_config);
|
|
env.cpu = cpu.get();
|
|
};
|
|
} |