diff --git a/CMakeLists.txt b/CMakeLists.txt index 796217d1..5cb0daea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ option(ENABLE_HTTP_SERVER "Enable HTTP server. Used for Discord bot support" OFF option(ENABLE_DISCORD_RPC "Compile with Discord RPC support (disabled by default)" ON) option(ENABLE_LUAJIT "Enable scripting with the Lua programming language" ON) option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF) +option(USE_KVM "Use KVM instead of Dynarmic" OFF) option(BUILD_HYDRA_CORE "Build a Hydra core" OFF) option(BUILD_LIBRETRO_CORE "Build a Libretro core" OFF) @@ -160,12 +161,14 @@ else() set(HOST_ARM64 FALSE) endif() -if(HOST_X64 OR HOST_ARM64) +if(NOT USE_KVM AND (HOST_X64 OR HOST_ARM64)) set(DYNARMIC_TESTS OFF) #set(DYNARMIC_NO_BUNDLED_FMT ON) set(DYNARMIC_FRONTENDS "A32" CACHE STRING "") add_subdirectory(third_party/dynarmic) add_compile_definitions(CPU_DYNARMIC) +elseif(USE_KVM AND HOST_ARM64) + add_compile_definitions(CPU_KVM) else() message(FATAL_ERROR "Currently unsupported CPU architecture") endif() diff --git a/include/cpu.hpp b/include/cpu.hpp index 14800e19..7c8a3f65 100644 --- a/include/cpu.hpp +++ b/include/cpu.hpp @@ -3,7 +3,7 @@ #ifdef CPU_DYNARMIC #include "cpu_dynarmic.hpp" #elif defined(CPU_KVM) -#error KVM CPU is not implemented yet +#include "cpu_kvm.hpp" #else #error No CPU core implemented :( #endif \ No newline at end of file diff --git a/include/cpu_kvm.hpp b/include/cpu_kvm.hpp new file mode 100644 index 00000000..5e3b2491 --- /dev/null +++ b/include/cpu_kvm.hpp @@ -0,0 +1,235 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "helpers.hpp" +#include "kernel.hpp" +#include "memory.hpp" + +#define AARCH64_CORE_REG(x) (KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM_CORE | KVM_REG_ARM_CORE_REG(x)) + +struct MmuTables { + u32 level1[4096]; + u32 level2SectionTables[256]; +}; + +constexpr u32 hypervisorCodeAddress = 0xD0000000; +constexpr u32 hypervisorDataAddress = 0xE0000000; +constexpr u32 hypervisorCodeSize = hypervisorDataAddress - hypervisorCodeAddress; +constexpr u32 hypervisorDataSize = hypervisorCodeSize; +constexpr u32 mmuTableOffset = hypervisorDataSize - sizeof(MmuTables); +constexpr u32 mmuTableAddress = hypervisorDataAddress + mmuTableOffset; +constexpr u32 exitCodeOffset = 0; // at start of hypervisor data segment +constexpr u32 customEntryOffset = 0x100000; // arbitrary, far enough that the exit code won't ever overlap with this +constexpr u32 guestStateOffset = 0x200000; // also arbitrary, store the guest state here upon exit + +struct GuestState +{ + std::array regs; + std::array fprs; + u32 cpsr; + u32 fpscr; + // u32 tlsBase? + // u64 ticks? +}; + +struct Environment { + Environment(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) { + u32 currentMemorySlot = 0; + + kvmDescriptor = open("/dev/kvm", O_RDWR); + if (kvmDescriptor < 0) { + Helpers::panic("Failed to open /dev/kvm"); + } + + vmDescriptor = ioctl(kvmDescriptor, KVM_CREATE_VM, 0); + if (vmDescriptor < 0) { + Helpers::panic("Failed to create KVM VM"); + } + + if (ioctl(vmDescriptor, KVM_CHECK_EXTENSION, KVM_CAP_ARM_EL1_32BIT) <= 0) { + Helpers::panic("CPU doesn't support EL1 32-bit mode, KVM won't work on this CPU"); + } + + // TODO: allocate these with mmap instead of malloc + kvm_userspace_memory_region vramRegionDesc = { + .slot = currentMemorySlot++, + .flags = 0, + .guest_phys_addr = PhysicalAddrs::VRAM, + .memory_size = PhysicalAddrs::VRAMSize, + .userspace_addr = (uint64_t)mem.getVRAM()}; + if (ioctl(vmDescriptor, KVM_SET_USER_MEMORY_REGION, &vramRegionDesc) < 0) { + Helpers::panic("Failed to set VRAM memory region"); + } + + kvm_userspace_memory_region dspRegionDesc = { + .slot = currentMemorySlot++, + .flags = 0, + .guest_phys_addr = PhysicalAddrs::DSPMem, + .memory_size = PhysicalAddrs::DSPMemSize, + .userspace_addr = (uint64_t)mem.getDSPMem()}; + if (ioctl(vmDescriptor, KVM_SET_USER_MEMORY_REGION, &dspRegionDesc) < 0) { + Helpers::panic("Failed to set DSP memory region"); + } + + kvm_userspace_memory_region fcramRegionDesc = { + .slot = currentMemorySlot++, + .flags = 0, + .guest_phys_addr = PhysicalAddrs::FCRAM, + .memory_size = PhysicalAddrs::FCRAMSize * 2, + .userspace_addr = (uint64_t)mem.getFCRAM()}; + if (ioctl(vmDescriptor, KVM_SET_USER_MEMORY_REGION, &fcramRegionDesc) < 0) { + Helpers::panic("Failed to set FCRAM memory region"); + } + + hypervisorCodeRegion = mmap(NULL, hypervisorCodeSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); + if (hypervisorCodeRegion == MAP_FAILED) { + Helpers::panic("Failed to allocate memory for hypervisor I/O"); + } + + kvm_userspace_memory_region hypervisorCodeRegionDesc = { + .slot = currentMemorySlot++, + .flags = KVM_MEM_READONLY, // We want writes here to cause VM exits + .guest_phys_addr = hypervisorCodeAddress, + .memory_size = hypervisorCodeSize, + .userspace_addr = (uint64_t)hypervisorCodeRegion + }; + + if (ioctl(vmDescriptor, KVM_SET_USER_MEMORY_REGION, &hypervisorCodeRegionDesc) < 0) { + Helpers::panic("Failed to set up hypervisor IO memory region\n"); + return; + } + + hypervisorDataRegion = mmap(NULL, hypervisorDataSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (hypervisorDataRegion == MAP_FAILED) { + Helpers::panic("Failed to allocate memory for hypervisor code"); + } + + kvm_userspace_memory_region hypervisorDataRegionDesc = { + .slot = currentMemorySlot++, + .flags = 0, + .guest_phys_addr = hypervisorDataAddress, + .memory_size = hypervisorDataSize, + .userspace_addr = (uint64_t)hypervisorDataRegion + }; + + if (ioctl(vmDescriptor, KVM_SET_USER_MEMORY_REGION, &hypervisorDataRegionDesc) < 0) { + Helpers::panic("Failed to set up hypervisor code memory region\n"); + return; + } + + cpuDescriptor = ioctl(vmDescriptor, KVM_CREATE_VCPU, 0); + if (cpuDescriptor < 0) { + Helpers::panic("Failed to create VCPU"); + } + + int mmapSize = ioctl(kvmDescriptor, KVM_GET_VCPU_MMAP_SIZE, 0); + if (mmapSize < 0) { + Helpers::panic("Failed to get KVM shared memory size"); + } + + runInfo = (kvm_run*)mmap(nullptr, mmapSize, PROT_READ | PROT_WRITE, MAP_SHARED, cpuDescriptor, 0); + if (runInfo == MAP_FAILED) { + Helpers::panic("Failed to map KVM shared memory"); + } + + kvm_vcpu_init initParams; + if (ioctl(vmDescriptor, KVM_ARM_PREFERRED_TARGET, &initParams) < 0) { + Helpers::panic("Failed to fetch initialization parameters for vCPU"); + } + initParams.features[0] |= 1 << KVM_ARM_VCPU_EL1_32BIT; + initParams.features[0] |= 1 << KVM_ARM_VCPU_PSCI_0_2; + + if (ioctl(cpuDescriptor, KVM_ARM_VCPU_INIT, initParams) < 0) { + Helpers::panic("Failed to initialize vCPU"); + } + + kvm_reg_list tempRegList; + tempRegList.n = 0; + ioctl(cpuDescriptor, KVM_GET_REG_LIST, &tempRegList); + + regList = (kvm_reg_list*)malloc(sizeof(kvm_reg_list) + tempRegList.n * sizeof(u64)); + regList->n = tempRegList.n; + if (ioctl(cpuDescriptor, KVM_GET_REG_LIST, regList) < 0) { + Helpers::panic("Failed to get register list"); + } + } + + void setPC(u32 pc) { + u64 val = (u64)pc; + kvm_one_reg reg; + + reg.id = AARCH64_CORE_REG(regs.pc); + reg.addr = (u64)&val; + + if (ioctl(cpuDescriptor, KVM_SET_ONE_REG, ®) < 0) [[unlikely]] { + printf("SetPC failed\n"); + } + } + + void run() { + if (ioctl(cpuDescriptor, KVM_RUN, 0) < 0) { + Helpers::panic("Failed to run vCPU"); + } else { + printf("KVM run succeeded\n"); + } + } + + void mapHypervisorCode(const std::vector& code, u32 offset) + { + if (code.size() > hypervisorCodeSize) { + Helpers::panic("Launch code is too big"); + } + memcpy((void*)((uintptr_t)hypervisorCodeRegion + offset), code.data(), code.size()); + } + + Memory& mem; + Kernel& kernel; + kvm_run* runInfo = nullptr; + kvm_reg_list* regList = nullptr; + void* hypervisorCodeRegion; + void* hypervisorDataRegion; + int kvmDescriptor = -1; + int vmDescriptor = -1; + int cpuDescriptor = -1; +}; + +class CPU { + Memory& mem; + Environment env; + GuestState state; + + public: + static constexpr u64 ticksPerSec = 268111856; + + CPU(Memory& mem, Kernel& kernel); + void reset() {} + + void setReg(int index, u32 value) {} + u32 getReg(int index) {return 0;} + + std::span regs() { return state.regs; } + std::span fprs() { return state.fprs; } + + void setCPSR(u32 value) { state.cpsr = value; } + u32 getCPSR() { return state.cpsr; } + void setFPSCR(u32 value) { state.fpscr = value; } + u32 getFPSCR() { return state.fpscr; } + void setTLSBase(u32 value) {} + + u64 getTicks() {return 0;} + u64& getTicksRef() {static u64 dummy; return dummy;} + + void clearCache() {} + + void runFrame() {} + + // TODO: remove + void romLoaded(); +}; + +#undef AARCH64_CORE_REG \ No newline at end of file diff --git a/include/memory.hpp b/include/memory.hpp index 2f01aa35..c2c0e717 100644 --- a/include/memory.hpp +++ b/include/memory.hpp @@ -17,12 +17,18 @@ namespace PhysicalAddrs { enum : u32 { VRAM = 0x18000000, - VRAMEnd = VRAM + 0x005FFFFF, + VRAMSize = 0x00600000, + VRAMEnd = VRAM + VRAMSize - 1, FCRAM = 0x20000000, FCRAMEnd = FCRAM + 0x07FFFFFF, DSP_RAM = 0x1FF00000, - DSP_RAM_End = DSP_RAM + 0x0007FFFF + DSP_RAM_End = DSP_RAM + 0x0007FFFF, + FCRAMSize = 0x08000000, + FCRAMEnd = FCRAM + FCRAMSize - 1, + DSPMem = 0x1FF00000, + DSPMemSize = 0x00080000, + DSPMemEnd = DSPMem + DSPMemSize - 1 }; } @@ -292,6 +298,7 @@ private: u8* getDSPDataMem() { return &dspRam[DSP_DATA_MEMORY_OFFSET]; } u8* getDSPCodeMem() { return &dspRam[DSP_CODE_MEMORY_OFFSET]; } u32 getUsedUserMem() { return usedUserMemory; } + u8* getVRAM() { return vram; } void setVRAM(u8* pointer) { vram = pointer; } void setDSPMem(u8* pointer) { dspRam = pointer; } diff --git a/perf.data b/perf.data new file mode 100644 index 00000000..0a51cd02 Binary files /dev/null and b/perf.data differ diff --git a/src/core/CPU/cpu_kvm.cpp b/src/core/CPU/cpu_kvm.cpp new file mode 100644 index 00000000..6bde71fd --- /dev/null +++ b/src/core/CPU/cpu_kvm.cpp @@ -0,0 +1,268 @@ +// #ifdef CPU_KVM +#include "cpu_kvm.hpp" + +MmuTables* mmuTables = nullptr; + +// ARMv6 MMU supports up to two levels of address lookup with 4KiB pages. +// The top level is called the level 1 table. It contains 4096 entries of 4 bytes each (16KiB total). +// The bottom level is called level 2, which contains 256 entries of 4 bytes each (1KiB total). +// The level 1 table supports 3 kind of entries: Pages, Sections and Supersections each corresponding to a page size. +// Pages are for 4KiB pages, Sections are for 1MiB pages and Supersections are for 16MiB pages. + +// Sections and supersections don't use the level 2 table at all. +// This is because with a 32 bit vaddr and 4 KiB pages, the offset is 12 bits, +// the level 2 index is 8 bits and the level 1 index is 12 bits -> 12 + 8 + 12 = 32 for the vaddr +// However for sections, the offset is 20 bits, so you can only use +// the level 1 table (up to 4096 entries) because 20 for offset + 12 for level 1 -> 20 + 12 = 32 for the vaddr +// For supersections, you need a 24 bit offset, so the level 1 table actually has up to 256 entries because +// you're left with 8 bits -> 24 + 8 = 32 for the vaddr + +// Level 2 entries +// Bits: 31-12 11 10 9 8-6 5-4 3 2 1 0 +// Value: BADDR nG S APX TEX[2:0] AP C B 1 XN + +// Access permission table: +/* + APX AP Privileged Unprivileged Description + 0 00 No access No access Permission fault + 0 01 Read/Write No access Privileged Access only + 0 10 Read/Write Read No user-mode write + 0 11 Read/Write Read/Write Full access + 1 00 - - Reserved + 1 01 Read No access Privileged Read only + 1 10 Read Read Read only + 1 11 - - Reserved +*/ + +constexpr u32 APX = 1 << 9; +constexpr u32 AP0 = 1 << 4; +constexpr u32 AP1 = 1 << 5; + +enum Level2Flags : u32 +{ + Level2Flags_ExecuteNever = 1 << 0, + Level2Flags_Bufferable = 1 << 2, + Level2Flags_Cacheable = 1 << 3, + Level2Flags_Shared = 1 << 10, + Level2Flags_AP_NoUserModeWrite = AP1, + Level2Flags_AP_FullAccess = AP1 | AP0, +}; + +// Generated by passing the following code to godbolt: +// Thanks libn3ds +/* + // FCSE PID Register (FCSE PID = 0) + // Note: This must be 0 before disabling the MMU otherwise UB + __asm__ volatile ("mcr p15, 0, %0, c13, c0, 0" : : "r"(0)); + + // Context ID Register (ASID = 0, PROCID = 0) + __asm__ volatile ("mcr p15, 0, %0, c13, c0, 1" : : "r"(0)); + + // // TTBR0 address shared page table walk and outer cachable write-through, no allocate on write + uint32_t ttbr0 = mmuTableAddress | 0x12; + __asm__ volatile ("mcr p15, 0, %0, c2, c0, 0" : : "r" (ttbr0) : "memory"); + + // Use the 16 KiB L1 table only + __asm__ volatile ("mcr p15, 0, %0, c2, c0, 2" : : "r"(0)); + + // Domain 0 = client, remaining domains all = no access + __asm__ volatile("mcr p15, 0, %0, c3, c0, 0" : : "r"(1)); + + uint32_t* d = (uint32_t*)hypervisorCodeAddress; + *d = hypervisorCodeAddress; +*/ +constexpr u8 mmuCodeBefore[] = { + 0x00, 0x30, 0xb0, 0xe3, // movs r3, #0 + 0x10, 0x3f, 0x0d, 0xee, // mcr p15, #0, r3, c13, c0, #0 + 0x30, 0x3f, 0x0d, 0xee, // mcr p15, #0, r3, c13, c0, #1 + 0x14, 0x20, 0x9f, 0xe5, // ldr r2, [pc, #0x14] + 0x10, 0x2f, 0x02, 0xee, // mcr p15, #0, r2, c2, c0, #0 + 0x50, 0x3f, 0x02, 0xee, // mcr p15, #0, r3, c2, c0, #2 + 0x01, 0x30, 0xb0, 0xe3, // movs r3, #1 + 0x10, 0x3f, 0x03, 0xee, // mcr p15, #0, r3, c3, c0, #0 + 0x0d, 0x32, 0xa0, 0xe3, // mov r3, #-0x30000000 TODO: instead jump to exit code + 0x00, 0x30, 0x83, 0xe5, // str r3, [r3] + (mmuTableAddress & 0xFF) | 0x12, (mmuTableAddress >> 8) & 0xFF, (mmuTableAddress >> 16) & 0xFF, (mmuTableAddress >> 24) & 0xFF, +}; + +// Generated by passing the following code to godbolt: +// Thanks libn3ds +/* + // Invalidate TLB + __asm__ volatile("mcr p15, 0, %0, c8, c7, 0" : : "r"(0)); + __asm__ volatile("dsb"); + + // Get ACR + uint32_t reg; + __asm__ volatile("mrc p15, 0, %0, c1, c0, 1" : "=r"(reg)); + // Enable Return stack, Dynamic branch prediction, Static branch prediction, + // Instruction folding and SMP mode: the CPU is taking part in coherency + reg |= 0x2F; + __asm__ volatile("mcr p15, 0, %0, c1, c0, 1" : : "r"(reg)); + + // Get CR + __asm__ volatile("mrc p15, 0, %0, c1, c0, 0" : "=r"(reg)); + // Enable MMU, D-Cache, Program flow prediction, + // I-Cache, high exception vectors, Unaligned data access, + // subpage AP bits disabled + reg |= 0xC03805; + __asm__ volatile("mcr p15, 0, %0, c1, c0, 0" : : "r"(reg)); + + // Invalidate both caches + __asm__ volatile("mcr p15, 0, %0, c7, c7, 0" : : "r" (0) : "memory"); + __asm__ volatile("dsb"); + __asm__ volatile("isb"); + + uint32_t* d = (uint32_t*)hypervisorCodeAddress; + *d = hypervisorCodeAddress; +*/ +constexpr u8 mmuCodeAfter[] = { + 0x00, 0x00, 0xb0, 0xe3, // movs r0, #0 + 0x17, 0x0f, 0x08, 0xee, // mcr p15, #0, r0, c8, c7, #0 + 0x4f, 0xf0, 0x7f, 0xf5, // dsb sy + 0x30, 0x3f, 0x11, 0xee, // mrc p15, #0, r3, c1, c0, #1 + 0x2f, 0x30, 0x83, 0xe3, // orr r3, r3, #0x2f + 0x30, 0x3f, 0x01, 0xee, // mcr p15, #0, r3, c1, c0, #1 + 0x10, 0x2f, 0x11, 0xee, // mrc p15, #0, r2, c1, c0, #0 + 0x05, 0x38, 0x03, 0xe3, // movw r3, #0x3805 + 0xc0, 0x30, 0x40, 0xe3, // movt r3, #0xc0 + 0x02, 0x30, 0x93, 0xe1, // orrs r3, r3, r2 + 0x10, 0x3f, 0x01, 0xee, // mcr p15, #0, r3, c1, c0, #0 + 0x17, 0x0f, 0x07, 0xee, // mcr p15, #0, r0, c7, c7, #0 + 0x4f, 0xf0, 0x7f, 0xf5, // dsb sy + 0x6f, 0xf0, 0x7f, 0xf5, // isb sy + 0x0d, 0x32, 0xa0, 0xe3, // mov r3, #-0x30000000 TODO: instead jump to exit code + 0x00, 0x30, 0x83, 0xe5, // str r3, [r3] +}; + +// Store the CPU state and exit the VM, then return from SVC +// Generated from the following ARM32 assembly +/* + push {r0} + ldr r0, GuestStateAddr + 4 + stmfd r0, {r1-r12, sp, lr, pc}^ + pop {r0} + + push {r1} + ldr r1, GuestStateAddr + str r0, [r1] + + // Exit the VM + ldr r1, CodeAddr + str r1, [r1] + + pop {r1} + + CodeAddr: + .word 0xD0000000 + GuestStateAddr: + .word 0xE0200000 +*/ +constexpr u8 svcHandlerCode[] = { +}; + +/// Level 1, page table entry +/// Bits: 31-10 9 8-5 4 3 2 1 0 +/// Value: BADDR IMP Domain SBZ NS PXN 0 1 +/// We don't use domains, so we can set it to 0 +u32 pageTableEntry(u32 level2Address) +{ + // Level 2 tables have 256 entries of 4 bytes each, so they must be aligned to 1KiB + if ((level2Address & 0x3FF) != 0) { + Helpers::panic("level2Address is not aligned to 1KiB"); + } + + return level2Address | 0b1; +} + +u32 level2Entry(u32 physicalAddress, Level2Flags flags) +{ + return (physicalAddress & 0xFFFFF000) | 0b10 | flags; +} + +void mapPageTables(u32 virtualAddress, u32 physicalAddress, u8 pageCount, Level2Flags flags) +{ + if ((virtualAddress & 0xFFFFF000) != 0) { + Helpers::panic("virtualAddress is not aligned to 4KiB"); + } + + if ((physicalAddress & 0xFFFFF000) != 0) { + Helpers::panic("physicalAddress is not aligned to 4KiB"); + } + + for (u32 i = 0; i < pageCount * 4096; i += 4096) + { + u8 level2Index = ((virtualAddress + i) >> 12) & 0xFF; + mmuTables->level2SectionTables[level2Index] = level2Entry(physicalAddress + i, flags); + } + + u32 level2TableAddressVm = mmuTableAddress + offsetof(MmuTables, level2SectionTables); + mmuTables->level1[virtualAddress >> 20] = pageTableEntry(level2TableAddressVm); +} + +CPU::CPU(Memory& mem, Kernel& kernel) +: mem(mem), env(mem, kernel) +{ +} + +void CPU::romLoaded() +{ + NCCH* ncch = mem.getCXI(); + if (!ncch) { + // TODO: what to do here? + Helpers::panic("Alber has decided to panic!"); + } + + // Map the VM exit code which stores all registers to shared hypervisor memory + // and exits the VM by writing to read-only memory. + // We map it at the start of hypervisorCodeAddress. + env.mapHypervisorCode(std::vector(vmExitCode, vmExitCode + sizeof(vmExitCode)), 0); + + printf("Debug: Running pre mmu table code\n"); + env.mapHypervisorCode(std::vector(mmuCodeBefore, mmuCodeBefore + sizeof(mmuCodeBefore)), customEntryOffset); + env.setPC(hypervisorCodeAddress + customEntryOffset); + env.run(); + + const auto& text = ncch->text; + const auto& rodata = ncch->rodata; + const auto& data = ncch->data; + + mmuTables = (MmuTables*)((uintptr_t)env.hypervisorDataRegion + mmuTableOffset); + printf("Debug: level2sectionTables is at %p in host, %08x in guest\n", mmuTables->level2SectionTables, mmuTableAddress + offsetof(MmuTables, level2SectionTables)); + mapPageTables( + text.address, + text.address, + text.pageCount, + (Level2Flags)(Level2Flags_Shared | + Level2Flags_Bufferable | + Level2Flags_Cacheable | + Level2Flags_AP_NoUserModeWrite) + ); + mapPageTables( + rodata.address, + rodata.address, + rodata.pageCount, + (Level2Flags)(Level2Flags_Shared | + Level2Flags_Bufferable | + Level2Flags_Cacheable | + Level2Flags_AP_NoUserModeWrite | + Level2Flags_ExecuteNever) + ); + mapPageTables( + data.address, + data.address, + data.pageCount, + (Level2Flags)(Level2Flags_Shared | + Level2Flags_Bufferable | + Level2Flags_Cacheable | + Level2Flags_AP_FullAccess) + ); + + printf("Debug: Running post mmu table code\n"); + env.mapHypervisorCode(std::vector(mmuCodeAfter, mmuCodeAfter + sizeof(mmuCodeAfter)), customEntryOffset); + env.setPC(hypervisorCodeAddress + customEntryOffset); + env.run(); + printf("Done\n"); +} + +// #endif