mirror of
https://github.com/pound-emu/pound.git
synced 2025-12-11 07:36:57 +00:00
Merge remote-tracking branch 'origin/aarch64'
Introduce the foundational support for emulating the AArch64 execution
state. This is the first major step towards running a guest operating
system.
1. vCPU State: A structure that models the architectural state of an
Arm64 vCPU. This includes the general-purpose registers, FP/SIMD
vector registers, and the essential EL1 system registers
(ELR_EL1, SPSR_EL1, ESR_EL1, FAR_EL1, VBAR_EL1) which are mandatory
for handling exceptions.
2. Guest Memory Model: A dedicated guest memory subsystem has been
created to manage the emulated physical address space.
3. Synchronous Exception Entry: This adds the core logic for taking
synchronous exceptions. The new take_synchronous_exception() function
emulates the hardware process of saving guest state, populating
syndrome registers, and preparing the vCPU to enter an exception
handler in EL1.
==========
GloriousTacoo (1):
aarch64/cpu: added system registers to vcpu_state_t (#67)
Ronald Caesar (9):
aarch64: Add core state structure for vCPU emulation
aarch64: Correct vCPU register state and add FP/SIMD support
aarch64/mem: Add fast GPA-to-HVA translation helper
aarch64/mem: Fixed pointer arithmatic warning
aarch64/mem: Introduce a dedicated guest memory access layer
memory: Move arena allocator into pound::memory namespace
aarch64/kernel: Add synchronous exception entry logic
arm64: Renames the aarch64 directory to arm64
arm64: Rename memory.h to guest.h
Sinan Karakaya (1):
feat(aarch64): added system registers to vcpu_state_t
Signed-off-by: Ronald Caesar <github43132@proton.me>
This commit is contained in:
commit
97f2dcce45
17 changed files with 526 additions and 193 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -26,7 +26,6 @@ x64/
|
|||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ bool ParseFilterRule(Filter &instance, Iterator begin, Iterator end) {
|
|||
CLS(System) \
|
||||
CLS(Render) \
|
||||
CLS(ARM) \
|
||||
CLS(Memory) \
|
||||
|
||||
// GetClassName is a macro defined by Windows.h, grrr...
|
||||
const char* GetLogClassName(Class logClass) {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ enum class Class : const u8 {
|
|||
System, // Base System messages
|
||||
Render, // OpenGL and Window messages
|
||||
ARM,
|
||||
Memory,
|
||||
Count // Total number of logging classes
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
|
||||
#include "jit.h"
|
||||
|
||||
#include <rem.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
using JitFunc = void (*)();
|
||||
|
||||
void JIT::translate_and_run(CPU& cpu)
|
||||
{
|
||||
|
||||
// TODO: Create REM Context
|
||||
create_rem_context(nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
|
||||
#ifdef WIN32
|
||||
u8* code = (u8*)VirtualAlloc(NULL, 64, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||
#else
|
||||
u8* code = (u8*)mmap(nullptr, 64, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
#endif
|
||||
|
||||
size_t offset = 0;
|
||||
|
||||
// Decode mock instructions from cpu.memory
|
||||
if (cpu.memory[0] == 0x05)
|
||||
{ // MOVZ placeholder
|
||||
code[offset++] = 0x48; // mov rax, imm64
|
||||
code[offset++] = 0xB8;
|
||||
u64 imm = 5;
|
||||
std::memcpy(&code[offset], &imm, sizeof(imm));
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
if (cpu.memory[4] == 0x03)
|
||||
{ // ADD placeholder
|
||||
code[offset++] = 0x48; // add rax, imm32
|
||||
code[offset++] = 0x05;
|
||||
u32 addval = 3;
|
||||
std::memcpy(&code[offset], &addval, sizeof(addval));
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
code[offset++] = 0xC3; // ret
|
||||
|
||||
JitFunc fn = reinterpret_cast<JitFunc>(code);
|
||||
u64 result;
|
||||
#if defined(__x86_64__)
|
||||
asm volatile(
|
||||
"call *%1\n"
|
||||
"mov %%rax, %0\n"
|
||||
: "=r"(result)
|
||||
: "r"(fn)
|
||||
|
||||
: "%rax");
|
||||
#elif defined(__aarch64__)
|
||||
asm volatile("blr %1\n"
|
||||
"mov %0, x0\n"
|
||||
: "=r"(result)
|
||||
: "r"(fn)
|
||||
: "x0");
|
||||
#endif
|
||||
|
||||
cpu.regs[0] = result;
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "aarch64/isa.h"
|
||||
|
||||
class JIT
|
||||
{
|
||||
public:
|
||||
void translate_and_run(CPU& cpu);
|
||||
};
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#include "isa.h"
|
||||
#include "Base/Assert.h"
|
||||
|
||||
void cpuTest()
|
||||
{
|
||||
CPU cpu;
|
||||
cpu.pc = 0;
|
||||
|
||||
// Simple ARMv8 program in memory (MOVZ X0, #5; ADD X0, X0, #3; RET)
|
||||
// These are placeholders; real encoding will be parsed later
|
||||
cpu.write_byte(0, 0x05); // MOVZ placeholder
|
||||
cpu.write_byte(4, 0x03); // ADD placeholder
|
||||
cpu.write_byte(8, 0xFF); // RET placeholder
|
||||
|
||||
LOG_INFO(ARM, "{}", cpu.read_byte(0));
|
||||
cpu.print_debug_information();
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "Base/Logging/Log.h"
|
||||
|
||||
namespace aarch64
|
||||
{
|
||||
#define GPR_REGISTERS 32
|
||||
#define ZERO_REGISTER_INDEX 31
|
||||
|
||||
#define FPR_REGISTERS 32
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint64_t gpr[GPR_REGISTERS];
|
||||
unsigned __int128 fpr[FPR_REGISTERS];
|
||||
uint64_t pc;
|
||||
uint64_t sp;
|
||||
} isa_t;
|
||||
|
||||
uint64_t read_X(uint64_t* registers, size_t n);
|
||||
void adr(uint64_t* registers, size_t n, uint64_t pc, uint64_t offset);
|
||||
//=========================================================
|
||||
// Access Floating Point Registers
|
||||
//=========================================================
|
||||
|
||||
uint8_t B(unsigned __int128 registers, size_t n);
|
||||
uint16_t H(unsigned __int128 registers, size_t n);
|
||||
uint32_t S(unsigned __int128 registers, size_t n);
|
||||
uint64_t D(unsigned __int128 registers, size_t n);
|
||||
unsigned __int128 Q(unsigned __int128 registers, size_t n);
|
||||
|
||||
} // namespace aarch64
|
||||
|
||||
struct CPU
|
||||
{
|
||||
u64 regs[31] = {0}; // X0–X30
|
||||
u64 pc = 0;
|
||||
static constexpr size_t MEM_SIZE = 64 * 1024;
|
||||
u8 memory[MEM_SIZE];
|
||||
|
||||
CPU() { std::memset(memory, 0, MEM_SIZE); }
|
||||
|
||||
u64& x(int i) { return regs[i]; }
|
||||
|
||||
u8 read_byte(u64 addr)
|
||||
{
|
||||
if (addr >= MEM_SIZE)
|
||||
{
|
||||
LOG_INFO(ARM, "{} out of bounds", addr);
|
||||
}
|
||||
return memory[addr];
|
||||
}
|
||||
|
||||
void write_byte(u64 addr, u8 byte)
|
||||
{
|
||||
if (addr >= MEM_SIZE)
|
||||
{
|
||||
LOG_INFO(ARM, "{} out of bounds", addr);
|
||||
}
|
||||
memory[addr] = byte;
|
||||
}
|
||||
|
||||
void print_debug_information()
|
||||
{
|
||||
LOG_INFO(ARM, "PC = {}", pc);
|
||||
for (int reg = 0; reg < 31; reg++)
|
||||
{
|
||||
uint64_t regis = x(reg);
|
||||
LOG_INFO(ARM, "X{} = {}", reg, regis); // X0 = 0..
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void cpuTest();
|
||||
7
core/arm64/guest.cpp
Normal file
7
core/arm64/guest.cpp
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#include "memory.h"
|
||||
#include "Base/Assert.h"
|
||||
|
||||
namespace pound::aarch64::memory
|
||||
{
|
||||
|
||||
} // namespace pound::aarch64::memory
|
||||
195
core/arm64/guest.h
Normal file
195
core/arm64/guest.h
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
#pragma once
|
||||
|
||||
#include "Base/Assert.h"
|
||||
|
||||
namespace pound::arm64::memory
|
||||
{
|
||||
|
||||
/*
|
||||
* guest_memory_t - Describes a contiguous block of guest physical RAM.
|
||||
* @base: Pointer to the start of the host-allocated memory block.
|
||||
* @size: The size of the memory block in bytes.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
uint8_t* base;
|
||||
uint64_t size;
|
||||
} guest_memory_t;
|
||||
|
||||
/*
|
||||
* gpa_to_hva() - Translate a Guest Physical Address to a Host Virtual Address.
|
||||
* @memory: The guest memory region to translate within.
|
||||
* @gpa: The Guest Physical Address (offset) to translate.
|
||||
*
|
||||
* This function provides a fast, direct translation for a flat guest memory
|
||||
* model. It relies on the critical pre-condition that the guest's physical
|
||||
* RAM is backed by a single, contiguous block of virtual memory in the host's
|
||||
* userspace (typically allocated with mmap()).
|
||||
*
|
||||
* In this model, memory->base is the Host Virtual Address (HVA) of the start of
|
||||
* the backing host memory. The provided Guest Physical Address (gpa) is not
|
||||
* treated as a pointer, but as a simple byte offset from the start of the guest's
|
||||
* physical address space (PAS).
|
||||
*
|
||||
* The translation is therefore a single pointer-offset calculation. This establishes
|
||||
* a direct 1:1 mapping between the guest's PAS and the host's virtual memory block.
|
||||
*
|
||||
* The function asserts that GPA is within bounds. The caller is responsible for
|
||||
* ensuring the validity of the GPA prior to calling.
|
||||
*
|
||||
* Return: A valid host virtual address pointer corresponding to the GPA.
|
||||
*/
|
||||
static inline uint8_t* gpa_to_hva(guest_memory_t* memory, uint64_t gpa)
|
||||
{
|
||||
ASSERT(nullptr != memory);
|
||||
ASSERT(nullptr != memory->base);
|
||||
ASSERT(gpa < memory->size);
|
||||
uint8_t* hva = memory->base + gpa;
|
||||
return hva;
|
||||
}
|
||||
|
||||
// TODO(GloriousTacoo:aarch64) Implement big to little endian conversion for guest_mem read and write functions.
|
||||
|
||||
/*
|
||||
* ============================================================================
|
||||
* Guest Memory Read Functions
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* guest_mem_readb() - Read one byte from guest memory.
|
||||
* @memory: The guest memory region.
|
||||
* @gpa: The Guest Physical Address to read from.
|
||||
* Returns the 8-bit value read from memory.
|
||||
*/
|
||||
static inline uint8_t guest_mem_readb(guest_memory_t* memory, uint64_t gpa)
|
||||
{
|
||||
ASSERT(nullptr != memory);
|
||||
ASSERT(nullptr != memory->base);
|
||||
ASSERT(gpa <= memory->size);
|
||||
uint8_t* hva = gpa_to_hva(memory, gpa);
|
||||
return *hva;
|
||||
}
|
||||
|
||||
/*
|
||||
* guest_mem_readw() - Read a 16-bit word from guest memory.
|
||||
* @memory: The guest memory region.
|
||||
* @gpa: The Guest Physical Address to read from (must be 2-byte aligned).
|
||||
* Returns the 16-bit value, corrected for host endianness.
|
||||
*/
|
||||
static inline uint16_t guest_mem_readw(guest_memory_t* memory, uint64_t gpa)
|
||||
{
|
||||
ASSERT(nullptr != memory);
|
||||
ASSERT(nullptr != memory->base);
|
||||
ASSERT((gpa + sizeof(uint16_t)) <= memory->size);
|
||||
// Check if gpa is aligned to 2 bytes.
|
||||
ASSERT((gpa & 1) == 0);
|
||||
uint16_t* hva = (uint16_t*)gpa_to_hva(memory, gpa);
|
||||
return *hva;
|
||||
}
|
||||
|
||||
/*
|
||||
* guest_mem_readl() - Read a 32-bit long-word from guest memory.
|
||||
* @memory: The guest memory region.
|
||||
* @gpa: The Guest Physical Address to read from (must be 4-byte aligned).
|
||||
* Returns the 32-bit value, corrected for host endianness.
|
||||
*/
|
||||
static inline uint32_t guest_mem_readl(guest_memory_t* memory, uint64_t gpa)
|
||||
{
|
||||
ASSERT(nullptr != memory);
|
||||
ASSERT(nullptr != memory->base);
|
||||
ASSERT((gpa + sizeof(uint32_t)) <= memory->size);
|
||||
// Check if gpa is aligned to 4 bytes.
|
||||
ASSERT((gpa & 3) == 0);
|
||||
uint32_t* hva = (uint32_t*)gpa_to_hva(memory, gpa);
|
||||
return *hva;
|
||||
}
|
||||
|
||||
/*
|
||||
* guest_mem_readq() - Read a 64-bit quad-word from guest memory.
|
||||
* @memory: The guest memory region.
|
||||
* @gpa: The Guest Physical Address to read from (must be 8-byte aligned).
|
||||
* Returns the 64-bit value, corrected for host endianness.
|
||||
*/
|
||||
static inline uint64_t guest_mem_readq(guest_memory_t* memory, uint64_t gpa)
|
||||
{
|
||||
ASSERT(nullptr != memory);
|
||||
ASSERT(nullptr != memory->base);
|
||||
ASSERT((gpa + sizeof(uint64_t)) <= memory->size);
|
||||
// Check if gpa is aligned to 8 bytes.
|
||||
ASSERT((gpa & 7) == 0);
|
||||
uint64_t* hva = (uint64_t*)gpa_to_hva(memory, gpa);
|
||||
return *hva;
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================================================================
|
||||
* Guest Memory Write Functions
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* guest_mem_writeb() - Write one byte to guest memory.
|
||||
* @memory: The guest memory region.
|
||||
* @gpa: The Guest Physical Address to write to.
|
||||
* @val: The 8-bit value to write.
|
||||
*/
|
||||
static inline void guest_mem_writeb(guest_memory_t* memory, uint64_t gpa, uint8_t val)
|
||||
{
|
||||
ASSERT(nullptr != memory);
|
||||
ASSERT(nullptr != memory->base);
|
||||
ASSERT(gpa <= memory->size);
|
||||
uint8_t* hva = gpa_to_hva(memory, gpa);
|
||||
*hva = val;
|
||||
}
|
||||
|
||||
/*
|
||||
* guest_mem_writew() - Write a 16-bit word to guest memory.
|
||||
* @memory: The guest memory region.
|
||||
* @gpa: The Guest Physical Address to write to (must be 2-byte aligned).
|
||||
* @val: The 16-bit value to write (will be converted to guest endianness).
|
||||
*/
|
||||
static inline void guest_mem_writew(guest_memory_t* memory, uint64_t gpa, uint16_t val)
|
||||
{
|
||||
ASSERT(nullptr != memory);
|
||||
ASSERT(nullptr != memory->base);
|
||||
ASSERT((gpa + sizeof(uint16_t)) <= memory->size);
|
||||
// Check if gpa is aligned to 2 bytes.
|
||||
ASSERT((gpa & 1) == 0);
|
||||
uint16_t* hva = (uint16_t*)gpa_to_hva(memory, gpa);
|
||||
*hva = val;
|
||||
}
|
||||
|
||||
/*
|
||||
* guest_mem_writel() - Write a 32-bit long-word to guest memory.
|
||||
* @memory: The guest memory region.
|
||||
* @gpa: The Guest Physical Address to write to (must be 4-byte aligned).
|
||||
* @val: The 32-bit value to write.
|
||||
*/
|
||||
static inline void guest_mem_writel(guest_memory_t* memory, uint64_t gpa, uint32_t val)
|
||||
{
|
||||
ASSERT(nullptr != memory->base);
|
||||
ASSERT((gpa + sizeof(uint32_t)) <= memory->size);
|
||||
// Check if gpa is aligned to 4 bytes.
|
||||
ASSERT((gpa & 3) == 0);
|
||||
uint32_t* hva = (uint32_t*)gpa_to_hva(memory, gpa);
|
||||
*hva = val;
|
||||
}
|
||||
|
||||
/*
|
||||
* guest_mem_writeq() - Write a 64-bit quad-word to guest memory.
|
||||
* @memory: The guest memory region.
|
||||
* @gpa: The Guest Physical Address to write to (must be 8-byte aligned).
|
||||
* @val: The 64-bit value to write.
|
||||
*/
|
||||
static inline void guest_mem_writeq(guest_memory_t* memory, uint64_t gpa, uint64_t val)
|
||||
{
|
||||
ASSERT(nullptr != memory);
|
||||
ASSERT(nullptr != memory->base);
|
||||
ASSERT((gpa + sizeof(uint64_t)) <= memory->size);
|
||||
// Check if gpa is aligned to 8 bytes.
|
||||
ASSERT((gpa & 7) == 0);
|
||||
uint64_t* hva = (uint64_t*)gpa_to_hva(memory, gpa);
|
||||
*hva = val;
|
||||
}
|
||||
} // namespace pound::aarch64::memory
|
||||
167
core/arm64/isa.cpp
Normal file
167
core/arm64/isa.cpp
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
#include "isa.h"
|
||||
#include "Base/Assert.h"
|
||||
#include "guest.h"
|
||||
#include "memory/arena.h"
|
||||
|
||||
namespace pound::arm64
|
||||
{
|
||||
void take_synchronous_exception(vcpu_state_t* vcpu, uint8_t exception_class, uint32_t iss, uint64_t faulting_address)
|
||||
{
|
||||
ASSERT(nullptr != vcpu);
|
||||
/* An EC holds 6 bits.*/
|
||||
ASSERT(0 == (exception_class & 11000000));
|
||||
/* An ISS holds 25 bits */
|
||||
ASSERT(0 == (iss & 0xFE000000));
|
||||
|
||||
vcpu->elr_el1 = vcpu->pc;
|
||||
vcpu->spsr_el1 = vcpu->pstate;
|
||||
vcpu->esr_el1 = 0;
|
||||
|
||||
/* Bits [31:26] are the Exception Class (EC). */
|
||||
/* Bits [25] is the Instruction Length (IL), 1 for a 32-bit instruction. */
|
||||
/* Bits [24:0] are the Instruction Specific Syndrome (ISS) */
|
||||
const uint64_t esr_il_bit = (1ULL << 25);
|
||||
vcpu->esr_el1 = ((uint64_t)exception_class << 26) | esr_il_bit | iss;
|
||||
|
||||
if ((exception_class == EC_DATA_ABORT) || (exception_class == EC_DATA_ABORT_LOWER_EL))
|
||||
{
|
||||
vcpu->far_el1 = faulting_address;
|
||||
}
|
||||
|
||||
/* The CPU state must be changed to a known safe state for handling */
|
||||
vcpu->pstate &= ~0xF0000000;
|
||||
|
||||
/* Mask asynchronous exceptions (IRQ, FIQ, SError). We dont want the
|
||||
* Exception handler to be interruoted by a less important event. */
|
||||
const uint32_t PSTATE_IRQ_BIT = (1 << 7);
|
||||
const uint32_t PSTATE_FIQ_BIT = (1 << 6);
|
||||
const uint32_t PSTATE_SERROR_BIT = (1 << 8);
|
||||
vcpu->pstate |= (PSTATE_IRQ_BIT | PSTATE_FIQ_BIT | PSTATE_SERROR_BIT);
|
||||
|
||||
/* Set the target exception level to EL1. The mode field M[3:0] is set
|
||||
* to 0b0101 for EL1h (using SP_EL1). (page 913 in manual) */
|
||||
const uint32_t PSTATE_EL_MASK = 0b1111;
|
||||
vcpu->pstate &= ~PSTATE_EL_MASK;
|
||||
const uint32_t PSTATE_EL1H = 0b0101;
|
||||
vcpu->pstate |= PSTATE_EL1H;
|
||||
|
||||
/* TODO(GloriousTacoo:arm): DO NOT IMPLEMENT UNTIL THE INSTRUCTION
|
||||
* DECODER IS FINISHED.
|
||||
*
|
||||
* Create an Exception Vector Table, determine
|
||||
* the address of the exception handler, then update the PC.
|
||||
*
|
||||
* vcpu->pc = vcpu->vbar_el1 + offset; */
|
||||
}
|
||||
|
||||
/** THIS FUNCTION WAS MADE WITH AI AND IS CALLED WHEN RUNNING THE CPU TEST FROM THE GUI!
|
||||
*
|
||||
* @brief Runs a comprehensive suite of tests on the guest memory access functions using the project's logging system.
|
||||
*
|
||||
* This function systematically tests the read and write capabilities of the memory
|
||||
* subsystem for all standard data sizes (8, 16, 32, and 64 bits). It verifies
|
||||
* that data written to memory can be correctly read back.
|
||||
*
|
||||
* It specifically checks:
|
||||
* 1. A standard aligned address in the middle of RAM.
|
||||
*
|
||||
* @param memory A pointer to an initialized guest_memory_t struct.
|
||||
* @return true if all tests pass, false otherwise.
|
||||
*/
|
||||
bool test_guest_ram_access(pound::arm64::memory::guest_memory_t* memory)
|
||||
{
|
||||
LOG_INFO(Memory, "--- [ Starting Guest RAM Access Test ] ---");
|
||||
if (memory == nullptr || memory->base == nullptr || memory->size < 4096)
|
||||
{
|
||||
LOG_CRITICAL(Memory, "Invalid memory block provided. Cannot run tests.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool all_tests_passed = true;
|
||||
|
||||
#define RUN_TEST(description, condition) \
|
||||
do \
|
||||
{ \
|
||||
char log_buffer[256]; \
|
||||
if (condition) \
|
||||
{ \
|
||||
snprintf(log_buffer, sizeof(log_buffer), " [TEST] %-45s... [PASS]", description); \
|
||||
LOG_INFO(Memory, log_buffer); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
snprintf(log_buffer, sizeof(log_buffer), " [TEST] %-45s... [FAIL]", description); \
|
||||
LOG_ERROR(Memory, log_buffer); \
|
||||
all_tests_passed = false; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define VERIFY_ACCESS(size, suffix, addr, write_val) \
|
||||
do \
|
||||
{ \
|
||||
guest_mem_write##suffix(memory, addr, write_val); \
|
||||
uint##size##_t read_val = guest_mem_read##suffix(memory, addr); \
|
||||
bool success = (read_val == write_val); \
|
||||
RUN_TEST("Write/Read " #size "-bit", success); \
|
||||
if (!success) \
|
||||
{ \
|
||||
char error_buffer[256]; \
|
||||
snprintf(error_buffer, sizeof(error_buffer), " -> At GPA 0x%016llx, Expected 0x%016llx, Got 0x%016llx", \
|
||||
(unsigned long long)addr, (unsigned long long)write_val, (unsigned long long)read_val); \
|
||||
LOG_ERROR(Memory, error_buffer); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// --- 1. Test a typical, aligned address in the middle of memory ---
|
||||
LOG_INFO(Memory, "[INFO] Testing standard access at a midrange address (GPA 0x1000)...");
|
||||
uint64_t test_addr = 0x1000;
|
||||
VERIFY_ACCESS(8, b, test_addr + 0, 0xA5);
|
||||
VERIFY_ACCESS(16, w, test_addr + 2, 0xBEEF);
|
||||
VERIFY_ACCESS(32, l, test_addr + 4, 0xDEADBEEF);
|
||||
VERIFY_ACCESS(64, q, test_addr + 8, 0xCAFEBABE01234567);
|
||||
|
||||
// --- 2. Test the very beginning of the memory block ---
|
||||
LOG_INFO(Memory, "[INFO] Testing boundary access at the start of RAM (GPA 0x0)...");
|
||||
VERIFY_ACCESS(64, q, 0x0, 0xFEEDFACEDEADBEEF);
|
||||
|
||||
// --- 3. Test the very end of the memory block ---
|
||||
LOG_INFO(Memory, "[INFO] Testing boundary access at the end of RAM...");
|
||||
|
||||
uint64_t end_addr_b = memory->size - 1;
|
||||
uint64_t end_addr_w = (memory->size - 2) & ~1ULL;
|
||||
uint64_t end_addr_l = (memory->size - 4) & ~3ULL;
|
||||
uint64_t end_addr_q = (memory->size - 8) & ~7ULL;
|
||||
|
||||
VERIFY_ACCESS(8, b, end_addr_b, 0xFE);
|
||||
VERIFY_ACCESS(16, w, end_addr_w, 0xFEFE);
|
||||
VERIFY_ACCESS(32, l, end_addr_l, 0xFEFEFEFE);
|
||||
VERIFY_ACCESS(64, q, end_addr_q, 0xFEFEFEFEFEFEFEFE);
|
||||
|
||||
// --- 4. Final Verdict ---
|
||||
LOG_INFO(Memory, "--- [ Guest RAM Access Test Finished ] ---");
|
||||
if (all_tests_passed)
|
||||
{
|
||||
LOG_INFO(Memory, ">>> Result: ALL TESTS PASSED");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR(Memory, ">>> Result: SOME TESTS FAILED");
|
||||
}
|
||||
LOG_INFO(Memory, "----------------------------------------------");
|
||||
|
||||
return all_tests_passed;
|
||||
}
|
||||
|
||||
void cpuTest()
|
||||
{
|
||||
vcpu_state_t vcpu_states[CPU_CORES] = {};
|
||||
pound::memory::arena_t guest_memory_arena = pound::memory::arena_init(GUEST_RAM_SIZE);
|
||||
ASSERT(nullptr != guest_memory_arena.data);
|
||||
|
||||
pound::arm64::memory::guest_memory_t guest_ram = {};
|
||||
guest_ram.base = static_cast<uint8_t*>(guest_memory_arena.data);
|
||||
guest_ram.size = guest_memory_arena.capacity;
|
||||
|
||||
(void)test_guest_ram_access(&guest_ram);
|
||||
}
|
||||
} // namespace pound::armv64
|
||||
123
core/arm64/isa.h
Normal file
123
core/arm64/isa.h
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
// Copyright 2025 Pound Emulator Project. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "Base/Logging/Log.h"
|
||||
|
||||
namespace pound::arm64
|
||||
{
|
||||
/* AArch64 R0-R31 */
|
||||
#define GP_REGISTERS 32
|
||||
|
||||
/* AArch64 V0-V31 */
|
||||
#define FP_REGISTERS 32
|
||||
|
||||
#define CACHE_LINE_SIZE 64
|
||||
#define GUEST_RAM_SIZE 10240 // 10KiB
|
||||
#define CPU_CORES 8
|
||||
|
||||
/* Data Abort exception taken without a change in Exception level. */
|
||||
#define EC_DATA_ABORT 0b100101
|
||||
|
||||
/* Data Abort exception from a lower Exception level. */
|
||||
#define EC_DATA_ABORT_LOWER_EL 0b100100
|
||||
|
||||
/*
|
||||
* vcpu_state_t - Holds the architectural and selected system-register state for an emulated vCPU.
|
||||
* @v: 128-bit SIMD/FP vector registers V0–V31.
|
||||
* @r: General-purpose registers X0–X31 (X31 as SP/ZR as appropriate).
|
||||
* @pc: Program Counter.
|
||||
* @cntfreq_el0: Counter Frequency.
|
||||
* @cntpct_el0: Physical Counter.
|
||||
* @cntvct_el0: Virtual Counter - CRITICAL for timing.
|
||||
* @cntv_cval_el0: Virtual Timer Compare Value.
|
||||
* @pmccntr_el0: Cycle Counter.
|
||||
* @tpidr_el0: Thread Pointer ID Register.
|
||||
* @tpidrro_el0: Thread Pointer ID, read-only.
|
||||
* @elr_el1: Exception Link Register.
|
||||
* @esr_el1: Exception Syndrome Register.
|
||||
* @far_el1: Fault Address Register.
|
||||
* @vbar_el1: Vector Base Address Register.
|
||||
* @spsr_el1: Saved Program Status Register.
|
||||
* @ctr_el0: Cache-Type.
|
||||
* @cntv_ctl_el0: Virtual Timer Control.
|
||||
* @dczid_el0: Data Cache Zero ID.
|
||||
* @pmcr_el0: Performance Monitor Counter.
|
||||
* @pstate: Process State Register (NZCV, DAIF, EL, etc.).
|
||||
*
|
||||
* This structure is aligned to the L1 cache line size to prevent false sharing
|
||||
* when multiple host threads are emulating vCPUs on different physical cores.
|
||||
*/
|
||||
typedef struct alignas(CACHE_LINE_SIZE)
|
||||
{
|
||||
unsigned __int128 v[FP_REGISTERS];
|
||||
uint64_t r[GP_REGISTERS];
|
||||
uint64_t pc;
|
||||
uint64_t cntfreq_el0;
|
||||
uint64_t cntpct_el0;
|
||||
uint64_t cntvct_el0;
|
||||
uint64_t cntv_cval_el0;
|
||||
uint64_t pmccntr_el0;
|
||||
uint64_t tpidr_el0;
|
||||
uint64_t tpidrro_el0;
|
||||
|
||||
/*
|
||||
* Stores the Program Counter of the instruction that was interrupted.
|
||||
* For a synchronous fault, it's the address of the faulting instruction
|
||||
* itself.
|
||||
*/
|
||||
uint64_t elr_el1;
|
||||
|
||||
/*
|
||||
* Tells the guest OS why the exception happened. It contains a high
|
||||
* level Exception Class (EC) (eg, Data Abort) and a low level
|
||||
* Instruction Specific Syndrome (ISS) with fine-grained details.
|
||||
* (eg, it was an allignment fault cause by a write operation.)
|
||||
*/
|
||||
uint64_t esr_el1;
|
||||
|
||||
/* The memory address that caused a Data Abort exception. */
|
||||
uint64_t far_el1;
|
||||
|
||||
/*
|
||||
* A snapshot of the current PSTATE register before the exception.
|
||||
* This is for restoring the program's state when returning from an
|
||||
* exception.
|
||||
*/
|
||||
uint64_t spsr_el1;
|
||||
|
||||
/*
|
||||
* The base address in guest memory where the Exception Vector Table
|
||||
* can be found.
|
||||
*/
|
||||
uint64_t vbar_el1;
|
||||
|
||||
uint32_t ctr_el0;
|
||||
uint32_t cntv_ctl_el0;
|
||||
uint32_t dczid_el0;
|
||||
uint32_t pmcr_el0;
|
||||
uint32_t pstate;
|
||||
} vcpu_state_t;
|
||||
|
||||
/*
|
||||
* take_synchronous_exception() - Emulates the hardware process of taking a synchronous exception to EL1.
|
||||
*
|
||||
* @vcpu: A pointer to the vCPU state to be modified.
|
||||
* @exception_class: The high-level Exception Class (EC) code for ESR_EL1.
|
||||
* @iss: The low-level Instruction Specific Syndrome (ISS) code for ESR_EL1.
|
||||
* @faulting_address: The faulting address, to be written to FAR_EL1. Only valid for Data/Instruction Aborts. Pass 0 for other exception types.
|
||||
*
|
||||
* This function modifies the vCPU state according to the rules for taking a
|
||||
* synchronous exception from a lower or same exception level that is targeting EL1.
|
||||
* It saves the necessary return state, populates the syndrome registers,
|
||||
* updates the processor state for entry into EL1, and calculates the new
|
||||
* program counter by branching to the appropriate offset in the EL1 vector table.
|
||||
*
|
||||
*/
|
||||
void take_synchronous_exception(vcpu_state_t* vcpu, uint8_t exception_class, uint32_t iss, uint64_t faulting_address);
|
||||
|
||||
void cpuTest();
|
||||
} // namespace pound::arm64
|
||||
19
core/arm64/mmu.h
Normal file
19
core/arm64/mmu.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
namespace pound::armv8
|
||||
/**
|
||||
* kvm_mmu_gva_to_gpa() - Translate a Guest Virtual Address to a Guest Physical Address.
|
||||
* @vcpu: The vCPU state, containing MMU configuration (TTBR0_EL1, etc.).
|
||||
* @gva: The Guest Virtual Address to translate.
|
||||
* @gpa: A pointer to store the resulting Guest Physical Address.
|
||||
*
|
||||
* This function is the entry point for the emulated MMU.
|
||||
*
|
||||
* For now, this is a stub that implements identity mapping (GVA -> GPA)
|
||||
* when the MMU is disabled, which is the correct architectural behavior
|
||||
* on reset.
|
||||
*
|
||||
* Return: 0 on success, or a negative error code on a fault.
|
||||
*/
|
||||
int kvm_mmu_gva_to_gpa(vcpu_state_t* vcpu, uint64_t gva, uint64_t* gpa);
|
||||
}
|
||||
|
|
@ -5,8 +5,8 @@
|
|||
#include <thread>
|
||||
|
||||
#include "Base/Config.h"
|
||||
#include "Base/Logging/Log.h"
|
||||
#include "Base/Logging/Backend.h"
|
||||
#include "JIT/jit.h"
|
||||
#include "gui/gui.h"
|
||||
#include "memory/arena.h"
|
||||
|
||||
|
|
@ -18,10 +18,6 @@
|
|||
|
||||
int main()
|
||||
{
|
||||
// This is meant to replace malloc() and its related functions.
|
||||
// TODO(GloriousTaco:memory): Implement std::allocator for this custom allocator which allows it to manage the memory of C++ standard types like std::vector.
|
||||
memory::arena_t arena = memory::arena_init(1024);
|
||||
|
||||
Base::Log::Initialize();
|
||||
Base::Log::Start();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
memory::arena_t memory::arena_init(size_t capacity)
|
||||
namespace pound::memory {
|
||||
arena_t arena_init(size_t capacity)
|
||||
{
|
||||
|
||||
// TODO(GloriousTaco:memory): Replace malloc with a windows memory mapping API.
|
||||
|
|
@ -28,7 +29,7 @@ memory::arena_t memory::arena_init(size_t capacity)
|
|||
}
|
||||
// new more memsafe code (ownedbywuigi) (i give up on windows compatibility for now, will stick to the old unsafe code)
|
||||
|
||||
const void* memory::arena_allocate(memory::arena_t* arena, const std::size_t size)
|
||||
const void* arena_allocate(memory::arena_t* arena, const std::size_t size)
|
||||
{
|
||||
ASSERT(arena != nullptr);
|
||||
ASSERT(arena->size + size <= arena->capacity);
|
||||
|
|
@ -36,14 +37,14 @@ const void* memory::arena_allocate(memory::arena_t* arena, const std::size_t siz
|
|||
arena->size += size;
|
||||
return data;
|
||||
}
|
||||
void memory::arena_reset(memory::arena_t* arena)
|
||||
void arena_reset(memory::arena_t* arena)
|
||||
{
|
||||
ASSERT(nullptr != arena);
|
||||
ASSERT(nullptr != arena->data);
|
||||
arena->size = 0;
|
||||
(void)std::memset(arena->data, POISON_PATTERN, arena->capacity);
|
||||
}
|
||||
void memory::arena_free(memory::arena_t* arena)
|
||||
void arena_free(memory::arena_t* arena)
|
||||
{
|
||||
ASSERT(arena != nullptr);
|
||||
arena->capacity = 0;
|
||||
|
|
@ -51,3 +52,4 @@ void memory::arena_free(memory::arena_t* arena)
|
|||
// TODO(GloriousTaco:memory): Replace free with a memory safe alternative.
|
||||
free(arena->data);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace memory
|
||||
namespace pound::memory
|
||||
{
|
||||
#define POISON_PATTERN 0xAA
|
||||
|
||||
|
|
@ -108,5 +108,5 @@ void arena_reset(arena_t* arena);
|
|||
*/
|
||||
void arena_free(memory::arena_t* arena);
|
||||
|
||||
} // namespace memory
|
||||
} // namespace pound::memory
|
||||
#endif //POUND_ARENA_H
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
#include <memory>
|
||||
#include "arena.h"
|
||||
|
||||
namespace memory
|
||||
namespace pound::memory
|
||||
{
|
||||
/**
|
||||
@brief An STL-compatible allocator that uses a custom arena for memory management.
|
||||
|
|
@ -65,4 +65,4 @@ namespace memory
|
|||
|
||||
}
|
||||
|
||||
#endif // POUND_ARENA_ALLOCATOR_H
|
||||
#endif // POUND_ARENA_ALLOCATOR_H
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include "panels.h"
|
||||
#include <math.h>
|
||||
#include "Base/Assert.h"
|
||||
#include "aarch64/isa.h"
|
||||
#include "arm64/isa.h"
|
||||
#include "imgui.h"
|
||||
|
||||
int8_t gui::panel::render_performance_panel(gui::panel::performance_panel_t* panel, performance_data_t* data,
|
||||
|
|
@ -94,7 +94,7 @@ int8_t gui::panel::render_cpu_panel(bool* show_cpu_result_popup)
|
|||
|
||||
if (::ImGui::Button("Run CPU Test", ImVec2(120, 0)))
|
||||
{
|
||||
::cpuTest();
|
||||
pound::arm64::cpuTest();
|
||||
*show_cpu_result_popup = true;
|
||||
}
|
||||
if (true == *show_cpu_result_popup)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue